重新介绍雄辩的多态关系

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内容达到最佳状态!



Laravel logo

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_idupvoteable_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.

AlbumSong模型中的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方法的问题,建议您创建一个AlbumUpvoteControllerSongUpvoteController 。 通过这种方式,我们可以使事情严格地与处理多态关系时正在执行的事情联系在一起。 就我们而言,我们可以同时发行专辑和歌曲。 上位票不是专辑的一部分,也不是歌曲的一部分。 而且,与大多数一对多关系中的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表中数据的屏幕截图:

Upvotes table

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可能属于AlbumSong ,默认的upvoteable_type分别为App\AlbumApp\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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多态是面向对象编程中的一个重要概念,指的是同一种操作可以在不同的对象类型上具有不同的行为。简单来说,多态可以让我们使用统一的接口来处理不同类型的对象。 在实现多态的过程中,通常会使用到继承和方法重写。继承是指一个类可以继承另一个类的属性和方法,子类可以拥有父类的特征。方法重写是指在子类中重新定义父类中已有的方法,使其具有不同的实现。 通过继承和方法重写,我们可以实现多态。假设有一个基类 Animal,它有一个共同的方法叫做 makeSound()。然后有两个子类 Dog 和 Cat,它们分别继承了 Animal 类,并且在子类中重写了 makeSound() 方法。 当我们使用多态的方式调用 makeSound() 方法时,可以根据实际对象的类型决定调用哪个子类的 makeSound() 方法。比如,如果我们创建了一个 Animal 类型的变量 animal,并将它赋值为 Dog 类型的对象,那么当我们调用 animal.makeSound() 时,实际上会调用 Dog 类中重写的 makeSound() 方法。 这样,无论 animal 是 Dog 类型还是 Cat 类型,我们都可以使用相同的方式来调用 makeSound() 方法,而得到不同的结果。这就是多态的作用,它使得我们可以以一种统一的方式处理不同类型的对象,并根据实际对象的类型来执行相应的操作。 总结一下,多态是通过继承和方法重写实现的,它允许我们使用相同的接口来处理不同类型的对象,提高了代码的灵活性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值