This article was peer reviewed by Younes Rafie. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
该文章由Younes Rafie进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
You’ve probably used different types of relationships between models or database tables, like those commonly seen in Laravel: one-to-one, one-to-many, many-to-many, and has-many-through. But there’s another type of relationship that’s not so common: polymorphic. So what is a polymorphic relationship?
您可能在模型或数据库表之间使用了不同类型的关系,例如Laravel中常见的关系:一对一,一对多,多对多和多对多。 但是还有另一种并不常见的关系:多态。 那么什么是多态关系?
A polymorphic relationship is where a model can belong to more than one other model on a single association.
多态关系是一个模型可以在单个关联上属于多个以上模型的关系。
To clarify this, let’s create an imaginary situation where we have a Topic
and a Post
model. Users can leave comments on both topics and posts. Using polymorphic relationships, we can use a single comments
table for both of these scenarios. Surprising, yeah? This seems a bit impractical since, ideally, we’d have to create a post_comments
table and a topic_comments
table to differentiate the comments. With polymorphic relationships, we don’t need two tables. Let’s look into polymorphic relationships through a practical example.
为了澄清这一点,让我们创建一个假想的情况,其中有一个Topic
和一个Post
模型。 用户可以对主题和帖子发表评论。 使用多态关系,我们可以在这两种情况下使用单个comments
表。 令人惊讶,是吗? 这似乎有点不实际,因为,理想情况下,我们不得不创建一个post_comments
表和topic_comments
表来区分的意见。 使用多态关系,我们不需要两个表。 让我们通过一个实际的例子来研究多态关系。
我们将要建设的 (What We’ll Be Building)
We’ll be creating a demo music app which has songs and albums. In this app, we’ll have the option to upvote both songs and albums. Using polymorphic relationships, we’ll use a single upvotes table for both of these scenarios. First, let’s examine the table structure required to build this relationship:
我们将创建一个演示音乐应用程序,其中包含歌曲和专辑。 在此应用中,我们可以选择同时歌曲和专辑。 使用多态关系,我们将在这两种情况下使用单个upvotes表。 首先,让我们检查建立这种关系所需的表结构:
albums
id - integer
name - string
songs
id - integer
title - string
album_id - integer
upvotes
id - integer
upvoteable_id - integer
upvoteable_type - string
Let’s talk about the upvoteable_id
and upvoteable_type
columns which may seem a bit foreign to those who’ve not used polymorphic relationships before. The upvoteable_id
column will contain the ID value of the album or song, while the upvoteable_type
column will contain the class name of the owning model. The upvoteable_type
column is how the ORM determines which “type” of owning model to return when accessing the upvoteable
relation.
让我们谈谈upvoteable_id
和upvoteable_type
列,对于那些以前没有使用多态关系的人来说似乎有些陌生。 upvoteable_id
列将包含专辑或歌曲的ID值,而upvoteable_type
列将包含拥有模型的类名称。 upvoteable_type
列是ORM如何确定在访问upvoteable
关系时要返回的拥有模型的“类型”。
与迁移一起生成模型 (Generating the Models Alongside Migrations)
I am assuming you already have a Laravel app that’s up and running. If not, this premium quick start course might help. Let’s start by creating the three models and migrations, then edit the migrations to suit our needs.
我假设您已经有一个Laravel应用程序正在运行。 如果没有,此高级快速入门课程可能会有所帮助。 首先创建三个模型和迁移,然后编辑迁移以适合我们的需求。
php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
Note, passing the -m
flag when creating models will generate migrations associated with those models as well. Let’s tweak the up
method in these migrations to get the desired table structure:
注意,在创建模型时传递-m
标志也将生成与那些模型相关的迁移。 让我们在这些迁移中调整up
方法以获得所需的表结构:
{some_timestamp}_create_albums_table.php
{some_timestamp} _create_albums_table.php
public function up()
{
Schema::create('albums', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
{some_timestamp}_create_songs_table.php
{some_timestamp} _create_songs_table.php
public function up()
{
Schema::create('songs', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->integer('album_id')->unsigned()->index();
$table->timestamps();
$table->foreign('album_id')->references('id')->on('album')->onDelete('cascade');
});
}
{some_timestamp}_create_upvotes_table.php
{some_timestamp} _create_upvotes_table.php
public function up()
{
Schema::create('upvotes', function (Blueprint $table) {
$table->increments('id');
$table->morphs('upvoteable'); // Adds unsigned INTEGER upvoteable_id and STRING upvoteable_type
$table->timestamps();
});
}
We can now run the artisan migrate
command to create the three tables:
现在,我们可以运行artisan migrate
命令来创建三个表:
php artisan migrate
Let’s now configure our models to take note of the polymorphic relationship between albums, songs, and upvotes:
现在,让我们配置模型以注意专辑,歌曲和upvotes之间的多态关系:
app/Upvote.php
app / Upvote.php
[...]
class Upvote extends Model
{
/**
* Get all of the owning models.
*/
public function upvoteable()
{
return $this->morphTo();
}
}
app/Album.php
app / Album.php
class Album extends Model
{
protected $fillable = ['name'];
public function songs()
{
return $this->hasMany(Song::class);
}
public function upvotes()
{
return $this->morphMany(Upvote::class, 'upvoteable');
}
}
app/Song.php
app / Song.php
class Song extends Model
{
protected $fillable = ['title', 'album_id'];
public function album()
{
return $this->belongsTo(Album::class);
}
public function upvotes()
{
return $this->morphMany(Upvote::class, 'upvoteable');
}
}
The upvotes
method in both the Album
and Song
models defines a polymorphic one-to-many relationship between these models and the Upvote
model and will help us get all of the upvotes for an instance of that particular model.
Album
和Song
模型中的upvotes
方法都定义了这些模型与Upvote
模型之间的多态一对多关系,这将帮助我们获得该特定模型实例的所有upvotes。
With the relationships defined, we can now play around with the app so as to get a better understanding of how polymorphic relationships work. We won’t create any views for this app, we’ll just tinker around with our application from the console.
定义了关系之后,我们现在可以使用该应用程序,以便更好地了解多态关系如何工作。 我们不会为此应用程序创建任何视图,我们只是从控制台修改应用程序。
In case you are thinking in terms of controllers and where we should place the upvote
method, I suggest creating an AlbumUpvoteController
and a SongUpvoteController
. By this, we kind of keep things strictly tied down to the thing we are acting on when working with polymorphic relationships. In our case, we can upvote both albums and songs. The upvote is not part of an album nor is it part of a song. Also, it’s not a general upvote thing, as opposed to how we’d have an UpvotesController
in most one-to-many relationships. Hopefully this makes sense.
如果您在考虑控制器以及应该在哪里放置upvote
方法的问题,建议您创建一个AlbumUpvoteController
和SongUpvoteController
。 通过这种方式,我们可以使事情严格地与处理多态关系时正在执行的事情联系在一起。 就我们而言,我们可以同时发行专辑和歌曲。 上位票不是专辑的一部分,也不是歌曲的一部分。 而且,与大多数一对多关系中的UpvotesController
相比,这不是一般的upvote事情。 希望这是有道理的。
Let’s fire up the console:
让我们启动控制台:
php artisan tinker
>>> $album = App\Album::create(['name' => 'More Life']);
>>> $song = App\Song::create(['title' => 'Free smoke', 'album_id' => 1]);
>>> $upvote1 = new App\Upvote;
>>> $upvote2 = new App\Upvote;
>>> $upvote3 = new App\Upvote;
>>> $album->upvotes()->save($upvote1)
>>> $song->upvotes()->save($upvote2)
>>> $album->upvotes()->save($upvote3)
检索关系 (Retrieving the Relationships)
Now that we have some data in place, we can access our relationships via our models. Below is a screenshot of the data inside the upvotes table:
现在我们已经有了一些数据,我们可以通过模型访问我们的关系。 以下是upvotes表中数据的屏幕截图:
To access all of the upvotes for an album, we can use the upvotes dynamic property:
要访问相册的所有支持,我们可以使用upvotes动态属性:
$album = App\Album::find(1);
$upvotes = $album->upvotes;
$upvotescount = $album->upvotes->count();
It’s also possible to retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphTo
. In our case, that is the upvoteable
method on the Upvote model. So, we will access that method as a dynamic property:
通过访问执行对morphTo
的调用的方法的名称,还可以从多态模型中检索多态关系的所有者。 在我们的例子,那就是upvoteable
上给予好评模型法。 因此,我们将以动态属性的形式访问该方法:
$upvote = App\Upvote::find(1);
$model = $upvote->upvoteable;
The upvoteable
relation on the Upvote model will return an Album
instance since this upvote is owned by an instance of the Album
instance.
Upvote模型上的upvoteable
关系将返回一个Album
实例,因为此upvote由Album
实例的实例拥有。
As it is possible to get the number of upvotes for a song or an album, we can sort the songs or albums based on the upvotes on a view. That is what happens in music charts.
由于可以获取歌曲或专辑的推荐数,因此我们可以基于视图上的赞对歌曲或专辑进行排序。 音乐排行榜就是这种情况。
In the case of a song we’d get the upvotes like so:
如果是一首歌,我们会像这样获得赞誉:
$song = App\Song::find(1);
$upvotes = $song->upvotes;
$upvotescount = $song->upvotes->count();
自定义多态类型 (Custom Polymorphic Types)
By default, Laravel will use the fully qualified class name to store the type of the related model. For instance, given the example above where an Upvote
may belong to an Album
or a Song
, the default upvoteable_type
would be either App\Album
or App\Song
, respectively.
默认情况下,Laravel将使用完全限定的类名来存储相关模型的类型。 例如,在上面的示例中, Upvote
可能属于Album
或Song
,默认的upvoteable_type
分别为App\Album
或App\Song
。
However, there is one big flaw with this. What if the namespace of the Album
model changes? We will have to make some sort of migration to rename all occurrences in the upvotes
table. And that’s a bit crafty! Also what happens in the case of long namespaces (such as App\Models\Data\Topics\Something\SomethingElse
)? That means we have to set a long max length on the column. And that is where the MorphMap
method comes to our rescue.
但是,这有一个大缺陷。 如果Album
模型的名称空间发生更改怎么办? 我们将必须进行某种迁移以重命名upvotes
表中的所有匹配upvotes
。 这有点狡猾! 在长名称空间(例如App\Models\Data\Topics\Something\SomethingElse
)的情况下还会发生App\Models\Data\Topics\Something\SomethingElse
? 这意味着我们必须在列上设置一个长的最大长度。 这就是MorphMap
方法的用武之地。
The “morphMap” method will instruct Eloquent to use a custom name for each model instead of the class name:
“ morphMap”方法将指示Eloquent为每个模型使用自定义名称而不是类名称:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'album' => \App\Album::class,
'song' => \App\Song::class,
]);
We can register the morphMap
in the boot function of our AppServiceProvider
or create a separate service provider. For the new changes to take effect, we have to run the composer dump-autoload
command. So now, we can add this new upvote record:
我们可以在AppServiceProvider
的启动功能中注册morphMap
或创建一个单独的服务提供商。 为了使新更改生效,我们必须运行composer dump-autoload
命令。 现在,我们可以添加此新的upvote记录:
[
"id" => 4,
"upvoteable_type" => "album",
"upvoteable_id" => 1
]
and it would behave in the exact same manner as the previous example does.
它的行为方式与前面的示例完全相同。
结论 (Conclusion)
Even though you’ve probably never run into a situation which required you to use polymorphic relationships, that day will likely eventually come. The good thing when working with Laravel is that it’s really easy to deal with this situation without having to do any kind of model association trickery to get things working. Laravel even supports many-to-many polymorphic relations. You can read more about that here.
即使您可能从未遇到过需要使用多态关系的情况,但这一天最终可能会到来。 与Laravel一起工作的好处是,无需进行任何模型关联欺骗就能使工作正常进行,这真的很容易处理。 Laravel甚至支持多对多的多态关系。 你可以在这里阅读更多有关它的内容。
I hope you’ve now understood polymorphic relationships and situations which may call for these types of relationships. Another, slightly more advanced example on Polymorphic relationships is available here. If you found this helpful, please share with your friends and don’t forget to hit the like button. Feel free to leave your thoughts in the comments section below!
希望您现在已经了解了可能需要这些类型的关系的多态关系和情况。 此处提供了另一个有关多态关系的高级示例。 如果您觉得这有帮助,请与您的朋友分享,不要忘记点击“赞”按钮。 随时在下面的评论部分中留下您的想法!
翻译自: https://www.sitepoint.com/eloquents-polymorphic-relationships-explained/