多态多态,顾名思义,关联得到的对象类型取决于当前状态。
多态关联允许一个模型在单个关联方法中关联一个以上其它模型。
多态关联是动态的,关联的对象取决于多态字段。
模型 A 多态关联模型 C,模型 C 对应的数据表必须有两个多态字段,其中一个多态字段存着模型 A 对应的数据表的主键,另一个多态字段存着模型 A 对应的数据表的表名称。
多态关联分为多态一对多关联和多态一对一关联。
以 ThinkPHP 官方手册的例子为例,例如用户可以评论书或文章,但评论表通常都是共用同一张数据表,多态一对多关联关系,就是为了满足类似的使用场景而设计。
以 ThinkPHP 官方手册的例子为例子,下面是关联表的数据表结构:
// 文章表
article
id - integer // 文章表主键
title - string // 文章标题
content - text // 文章内容
// 书籍表
book
id - integer // 书籍表主键
title - string // 书籍标题
// 评论表
comment
id - integer // 评论表主键
content - text // 评论内容
commentable_id - integer // 多态 id 字段,当前例子存文章表或书籍表的主键
commentable_type - string // 多态类型字段,当前例子存 article 或 book
一个文章(书籍)有多个评论(一对多),文章表和书籍表共用同一张评论表,这就是多态一对多。如果要使用多态关联,评论表必须设计成这样的结构。
有两个需要注意的字段是 comment
表中的 commentable_id
字段和 commentable_type
字段我们称之为多态字段。其中,commentable_id
用于存放书籍表或者文章表的 id(主键),而 commentable_type
用于存放该评论所属的表的表名称(article
或 book
),通过这样的表设计,我们可以清楚的知道当前评论属于哪篇文章或哪本书籍。通常多态字段的设计是有一个公共的前缀(例如这里用的 commentable
),当然,也支持设置完全不同的字段名(例如用 data_id
和 type
)。
文章表模型:
morphMany('Comment', 'commentable');
// 完整写法
// return $this->morphMany('Comment', 'commentable', 'Article');
}
}
morphMany
方法的参数如下:
morphMany('关联模型', '多态字段前缀或数组', '多态类型');
关联模型(必须):关联的模型名称,可以使用模型名(如 Comment
)或者完整的命名空间模型名(如 app\index\model\Comment
)。
多态字段前缀或数组(可选):如果是字符串则表示多态字段的前缀(多态前缀_type
和 多态前缀_id)
,如果是数组,使用 ['多态类型字段名', '多态ID字段名'] 的格式,默认为当前的关联方法名作为字段前缀(当前例子默认为 comments
)。
多态类型(可选):当前模型对应的多态类型,默认为当前模型名,可以使用模型名(如 Article 或 Book
)或者完整的命名空间模型名(如 app\index\model\Article
或
app\index\model\Book
)。
书籍表模型:
morphMany('Comment', 'commentable');
// 完整写法
// return $this->morphMany('Comment', 'commentable', 'Book');
}
}
书籍模型的设置方法同文章模型一致,区别在于多态类型不同,但由于多态类型默认会取当前模型名,因此不需要单独设置。
评论表模型
morphTo(); // 当前例子获取 Article 模型或 Book 模型
}
}
morphTo
方法的参数如下:
morphTo('多态字段前缀或数组', ['多态类型别名']);
多态字段前缀或数组(可选):如果是字符串则表示多态字段的前缀,如果是数组,使用['多态类型字段名', '多态ID字段名'] 的格式,默认为当前的关联方法名作为多态字段前缀(当前例子为 commentable
)。
多态类型别名(可选):数组方式定义。
Comment
模型的 commentable
关联会返回 Article
或 Book
模型的对象实例,这取决于评论的多态类型字段值。
morphTo
方法正如我在文章开头说的那样,多态关联允许一个模型在单个关联方法中关联一个以上其它模型。
多态一对一关联与多态一对多关联的区别就像一对一关联和一对多关联。以 ThinkPHP 官方手册的例子为例,有一个个人表和一个团队表,而无论是个人还是团队都有一个头像需要保存,而且他们共用同一张头像表,并且无论是个人还是团队都只能拥有一个头像,数据表结构如下
// 会员表
member
id - integer // 会员表主键
name - string
// 团队表
team
id - integer // 团队表主键
name - string
// 头像表
avatar
id - integer // 头像表主键
avatar - string
avatartable_id - integer // 多态 id 字段,当前例子存会员表主键或团队表主键
avatartable_type - string // 多态类型字段,当前例子存 member 或 team
一位会员或一个团队都是只有一个头像(一对一),会员表和团队表共用同一张头像表,这就是多态一对一。如果要使用多态关联,头像表必须设计成这样的表结构。
avatar
表中的 avatartable_id
字段和 avatar_type
字段我们称之为多态字段。其中,avatartable_id
用于存放会员表或者团队表的 id(主键),而 avatar_type
用于存放头像所属的表的表名称( member
或 team
) ,通过这样的表设计,我们可以清楚的知道当前头像属于哪位会员或哪个团队。通常多态字段的设计是有一个公共的前缀(例如这里用的 avatartable
),当然,也支持设置完全不同的字段名(例如用 data_id
和 type
)
会员模型:
morphOne('Avatar', 'avatartable');
// 完整写法
// return $this->morphOne('Avatar', 'avatartable', 'Member');
}
}
morphOne
方法的参数如下:
morphOne('关联模型', '多态字段前缀或数组', '多态类型');
关联模型(必须):关联的模型名称,可以使用模型名(如 Avatar
)或者完整的命名空间模型名(如 app\index\model\Avatar
)。
多态字段前缀或数组(可选):如果是字符串则表示多态字段的前缀(多态前缀_type
和 多态前缀_id)
,如果是数组,使用 ['多态类型字段名', '多态ID字段名'] 的格式,默认为当前的关联方法名作为字段前缀(当前例子默认为 avatar
)。
多态类型(可选):当前模型对应的多态类型,默认为当前模型名(Member
),可以使用模型名(如 Member
)或者完整的命名空间模型名(如 app\index\model\Member
)。
团队模型:
morphOne('Avatar', 'avatartable');
// 完整写法
// return $this->morphOne('Avatar', 'avatartable', 'Team');
}
}
团队模型的设置方法同会员模型一致,区别在于多态类型不同,但由于多态类型默认会取当前模型名,因此不需要单独设置。
头像模型:
morphTo(); // 当前例子获取 Member 模型或 Team 模型
}
}
morphTo
方法的参数如下:
morphTo('多态字段前缀或数组', ['多态类型别名']);
多态字段前缀或数组(可选):如果是字符串则表示多态字段的前缀,如果是数组,使用['多态类型字段名', '多态ID字段名'] 的格式,默认为当前的关联方法名作为多态字段前缀(当前例子默认为 avatartable
)。
多态类型别名(可选):数组方式定义。
Avatar
模型的 avatartable
关联会返回 Member
或 Team
模型的对象实例,这取决于头像的多态类型字段值。
morphTo 方法正像我在文章开头说的那样,多态关联允许一个模型在单个关联方法中关联一个以上其它模型。
理解了多态一对多关联后,多态一对一关联其实就很容易理解了,区别就是当前模型和动态关联的模型之间的关联属于一对一关系。
多态多态,顾名思义,关联得到的对象类型取决于当前状态。
多态关联允许一个模型在单个关联方法中关联一个以上其它模型。
多态关联是动态的,关联的对象取决于多态字段。
shop_article 表
shop_comment 表
shop_video表
获取 article_id 为 1 的文章详情以及属于它的评论,一篇文章有多个评论,所以使用的是多态一对多 morphMany
方法。
Article 控制器
Article 模型
morphMany('Comment', 'comment_table', 'article');
}
}
Comment 模型
Article 控制器 的 detail 方法输出如下
array(7) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-15 14:12:46"
["update_time"] => string(19) "2023-07-15 14:12:46"
["comments"] => object(think\Collection)#50 (1) {
["items":protected] => array(2) {
[0] => object(app\api\model\Comment)#45 (2) {
["data"] => array(6) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689401637)
["update_time"] => int(1689401637)
}
["relation"] => array(0) {
}
}
[1] => object(app\api\model\Comment)#46 (2) {
["data"] => array(6) {
["comment_id"] => int(2)
["content"] => string(36) "文章写得很好,下次别写了"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689403338)
["update_time"] => int(1689403338)
}
["relation"] => array(0) {
}
}
}
}
}
关联方法名 comments 作为键名,关联查询结果集对象 Collection 作为键值。
使用到的 SQL
SELECT * FROM `shop_article` WHERE `article_id` = 1 LIMIT 1
SELECT * FROM `shop_comment` WHERE `comment_table_id` = 1 AND `comment_table_type` = 'article'
获取文章的评论
comments);
}
}
访问关联方法名即可,输出如下
object(think\Collection)#50 (1) {
["items":protected] => array(2) {
[0] => object(app\api\model\Comment)#45 (2) {
["data"] => array(6) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689401637)
["update_time"] => int(1689401637)
}
["relation"] => array(0) {
}
}
[1] => object(app\api\model\Comment)#46 (2) {
["data"] => array(6) {
["comment_id"] => int(2)
["content"] => string(36) "文章写得很好,下次别写了"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689403338)
["update_time"] => int(1689403338)
}
["relation"] => array(0) {
}
}
}
}
获取所有的文章以及属于它自己的评论
Article 控制器
Article 模型
select();
}
// 多态一对多获取文章的所有评论
public function comments()
{
return $this->morphMany('Comment', 'comment_table', 'article');
}
}
Article 控制器的 list 方法输出
object(think\Collection)#43 (1) {
["items":protected] => array(2) {
[0] => object(app\api\model\Article)#41 (2) {
["data"] => array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => int(1689401566)
["update_time"] => int(1689401566)
}
["relation"] => array(1) {
["comments"] => object(think\Collection)#51 (1) {
["items":protected] => array(2) {
[0] => object(app\api\model\Comment)#46 (2) {
["data"] => array(6) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689401637)
["update_time"] => int(1689401637)
}
["relation"] => array(0) {
}
}
[1] => object(app\api\model\Comment)#47 (2) {
["data"] => array(6) {
["comment_id"] => int(2)
["content"] => string(36) "文章写得很好,下次别写了"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689403338)
["update_time"] => int(1689403338)
}
["relation"] => array(0) {
}
}
}
}
}
}
[1] => object(app\api\model\Article)#42 (2) {
["data"] => array(6) {
["article_id"] => int(2)
["title"] => string(13) "测试标题1"
["content"] => string(13) "测试内容1"
["user_id"] => int(1)
["create_time"] => int(1689498967)
["update_time"] => int(1689498967)
}
["relation"] => array(1) {
["comments"] => object(think\Collection)#52 (1) {
["items":protected] => array(0) {
}
}
}
}
}
}
每个文章模型对象的 relation 属性(关联数组)保存着关联查询得到的结果集对象,其中键名是所用到的关联方法名,键值是结果集对象,结果集对象的 items 属性(数组)保存着关联查询结果。
使用的 SQL
SELECT * FROM `shop_article`
SELECT * FROM `shop_comment` WHERE `comment_table_id` IN (1,2) AND `comment_table_type` = 'article'
获取每篇文章的评论
访问关联方法名即可,输出如下
object(think\Collection)#51 (1) {
["items":protected] => array(2) {
[0] => object(app\api\model\Comment)#46 (2) {
["data"] => array(6) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689401637)
["update_time"] => int(1689401637)
}
["relation"] => array(0) {
}
}
[1] => object(app\api\model\Comment)#47 (2) {
["data"] => array(6) {
["comment_id"] => int(2)
["content"] => string(36) "文章写得很好,下次别写了"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689403338)
["update_time"] => int(1689403338)
}
["relation"] => array(0) {
}
}
}
}
object(think\Collection)#52 (1) {
["items":protected] => array(0) {
}
}
获取 comment_id 为 1 的评论详情以及关联它的多态类型,使用的是 morphTo 方法。
comment 控制器
comment 模型
morphTo();
// 完整写法
// return $this->morphTo('comment_table');
}
}
comment 控制器的 detail 方法输出
array(7) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => string(19) "2023-07-15 14:13:57"
["update_time"] => string(19) "2023-07-15 14:13:57"
["comment_table"] => array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-15 14:12:46"
["update_time"] => string(19) "2023-07-15 14:12:46"
}
}
关联方法名 comment_table 作为键名,关联查询结果作为键值。
使用的 SQL
SELECT * FROM `shop_comment` WHERE `comment_id` = 1 LIMIT 1
SELECT * FROM `shop_article` WHERE `article_id` = 1 LIMIT 1
获取评论关联的多态类型
comment_table);
}
}
访问关联方法名即可,输出如下
array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-15 14:12:46"
["update_time"] => string(19) "2023-07-15 14:12:46"
}
获取所有评论以及评论对应的多态类型
comment 控制器
comment 模型
select();
}
// 获取评论对应的多态模型
public function commentTable()
{
return $this->morphTo();
}
}
Comment 控制器 list 方法输出
object(think\Collection)#44 (1) {
["items":protected] => array(3) {
[0] => object(app\api\model\Comment)#40 (2) {
["data"] => array(6) {
["comment_id"] => int(1)
["content"] => string(30) "文章写得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689401637)
["update_time"] => int(1689401637)
}
["relation"] => array(1) {
["comment_table"] => object(app\api\model\Article)#47 (2) {
["data"] => array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => int(1689401566)
["update_time"] => int(1689401566)
}
["relation"] => array(0) {
}
}
}
}
[1] => object(app\api\model\Comment)#42 (2) {
["data"] => array(6) {
["comment_id"] => int(2)
["content"] => string(36) "文章写得很好,下次别写了"
["comment_table_id"] => int(1)
["comment_table_type"] => string(7) "article"
["create_time"] => int(1689403338)
["update_time"] => int(1689403338)
}
["relation"] => array(1) {
["comment_table"] => object(app\api\model\Article)#47 (2) {
["data"] => array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => int(1689401566)
["update_time"] => int(1689401566)
}
["relation"] => array(0) {
}
}
}
}
[2] => object(app\api\model\Comment)#43 (2) {
["data"] => array(6) {
["comment_id"] => int(3)
["content"] => string(30) "视频做得很好,点个赞"
["comment_table_id"] => int(1)
["comment_table_type"] => string(5) "video"
["create_time"] => int(1689473711)
["update_time"] => int(1689473711)
}
["relation"] => array(1) {
["comment_table"] => object(app\api\model\Video)#46 (2) {
["data"] => array(5) {
["video_id"] => int(1)
["title"] => string(12) "视频测试"
["user_id"] => int(1)
["create_time"] => int(1689473536)
["update_time"] => int(1689473536)
}
["relation"] => array(0) {
}
}
}
}
}
}
使用的 SQL
SELECT * FROM `shop_comment`
SELECT * FROM `shop_article` WHERE `article_id` = 1
SELECT * FROM `shop_video` WHERE `video_id` = 1
获取每个评论关联的多态类型
访问关联方法名即可,输出如下
array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-15 14:12:46"
["update_time"] => string(19) "2023-07-15 14:12:46"
}
array(6) {
["article_id"] => int(1)
["title"] => string(12) "测试标题"
["content"] => string(12) "测试内容"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-15 14:12:46"
["update_time"] => string(19) "2023-07-15 14:12:46"
}
array(5) {
["video_id"] => int(1)
["title"] => string(12) "视频测试"
["user_id"] => int(1)
["create_time"] => string(19) "2023-07-16 10:12:16"
["update_time"] => string(19) "2023-07-16 10:12:16"
}