0
点赞
收藏
分享

微信扫一扫

重新引入 Eloquent 的多态关系

Greatiga 2022-03-22 阅读 21

你可能在模型或数据库表之间使用过不同类型的关系,就像在 Laravel 中常见的那样:一对一、一对多、多对多和多对多。但是还有另一种不常见的关系:多态。那么什么是多态关系呢?
多态关系是指一个模型在单个关联上可以属于多个其他模型。
为了澄清这一点,让我们创建一个假想的情况,我们有一个Topic和一个Post模型。用户可以对主题和帖子发表评论。comments使用多态关系,我们可以为这两种情况使用一个表。令人惊讶,是吗?这似乎有点不切实际,因为理想情况下,我们必须创建一个post_comments表和一个topic_comments表来区分注释。对于多态关系,我们不需要两个表。让我们通过一个实际的例子来研究多态关系。
我们将建造什么
我们将创建一个包含歌曲和专辑的演示音乐应用程序。在这个应用程序中,我们可以选择对歌曲和专辑进行投票。使用多态关系,我们将为这两种情况使用一个支持表。首先,让我们检查建立这种关系所需的表结构:
albums
id - integer
name - string
songs
id - integer
title - string
album_id - integer
upvotes
id - integer
upvoteable_id - integer
upvoteable_type - string
让我们谈谈对于那些以前没有使用过多态关系的人来说可能看起来有点陌生的upvoteable_idand列。upvoteable_type该upvoteable_id列将包含专辑或歌曲的 ID 值,而该upvoteable_type列将包含拥有模型的类名。该列是 ORM 在访问关系upvoteable_type时如何确定要返回的拥有模型的“类型” 。upvoteable
与迁移一起生成模型
我假设你已经有一个启动并运行的 Laravel 应用程序。如果没有,这个高级快速入门课程可能会有所帮助。让我们从创建三个模型和迁移开始,然后编辑迁移以满足我们的需要。
php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
请注意,在创建模型时传递-m标志也会生成与这些模型关联的迁移。让我们调整up这些迁移中的方法以获得所需的表结构:
{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
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
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();
});
}
我们现在可以运行 artisanmigrate命令来创建三个表:
php artisan migrate
现在让我们配置我们的模型来记录专辑、歌曲和点赞之间的多态关系:
应用程序/Upvote.php
[...]
class Upvote extends Model
{
/**

  • Get all of the owning models.

*/
public function upvoteable()
{
return $this->morphTo();
}
}
应用程序/相册.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');
}
}
应用程序/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');
}
}
和模型中的upvotes方法定义了这些模型和模型之间的多态一对多关系,并将帮助我们获得该特定模型实例的所有赞成票。AlbumSongUpvote
定义好关系后,我们现在可以玩转应用程序,以便更好地了解多态关系的工作原理。我们不会为这个应用程序创建任何视图,我们只会从控制台修改我们的应用程序。
如果您正在考虑控制器以及我们应该将upvote方法放在哪里,我建议创建一个AlbumUpvoteController和一个SongUpvoteController. 通过这种方式,我们在处理多态关系时将事情严格地绑定到我们正在执行的事情上。在我们的例子中,我们可以对专辑和歌曲进行投票。upvote 不是专辑的一部分,也不是歌曲的一部分。此外,与我们UpvotesController在大多数一对多关系中的关系相反,这不是普遍的支持。希望这是有道理的。
让我们启动控制台:
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)

检索关系
现在我们有了一些数据,我们可以通过我们的模型访问我们的关系。以下是upvotes 表中数据的屏幕截图:
点赞表
要访问专辑的所有点赞,我们可以使用 upvotes 动态属性:
$album = App\Album::find(1);
$upvotes = $album->upvotes;
$upvotescount = $album->upvotes->count();
也可以通过访问执行调用的方法的名称从多态模型中检索多态关系的所有者morphTo。在我们的例子中,这就是upvoteableUpvote 模型上的方法。因此,我们将该方法作为动态属性访问:
$upvote = App\Upvote::find(1);
$model = $upvote->upvoteable;
Upvote 模型上的upvoteable关系将返回一个Album实例,因为该 upvote 由该实例的Album实例拥有。
由于可以获得歌曲或专辑的点赞数,因此我们可以根据视图上的点赞数对歌曲或专辑进行排序。这就是音乐排行榜中发生的事情。
在一首歌的情况下,我们会得到这样的支持:
$song = App\Song::find(1);
$upvotes = $song->upvotes;
$upvotescount = $song->upvotes->count();
自定义多态类型
默认情况下,Laravel 将使用完全限定的类名来存储相关模型的类型。例如,给定上面的示例,其中 anUpvote可能属于 anAlbum或 a Song,默认值 upvoteable_type将分别为App\Albumor 或App\Song。
然而,这有一个很大的缺陷。如果Album模型的命名空间发生变化怎么办?我们将不得不进行某种迁移来重命名upvotes表中的所有匹配项。这有点狡猾!在长名称空间(例如App\Models\Data\Topics\Something\SomethingElse)的情况下会发生什么?这意味着我们必须在列上设置一个较长的最大长度。这就是该MorphMap方法可以帮助我们的地方。
“morphMap”方法将指示 Eloquent 为每个模型使用自定义名称而不是类名:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'album' => \App\Album::class,
'song' => \App\Song::class,
]);
我们可以morphMap在我们的启动函数中注册AppServiceProvider或者创建一个单独的服务提供者。要使新更改生效,我们必须运行该composer dump-autoload命令。所以现在,我们可以添加这个新的投票记录:
[
"id" => 4,
"upvoteable_type" => "album",
"upvoteable_id" => 1
]
它的行为方式与前面的示例完全相同。

举报

相关推荐

0 条评论