今天是Eloquent的Polymorphic關係系列,但其實它跟一開始的一對一/一對多/多對多關係系列並沒有太大的差別,最大的不同在於一個Model現在可以跟多個Model連結。
以官方文件的例子說明:
Post和User擁有一個Image,一個Image屬於Post或User:
如果以非Polymorphic 的一對一關係來寫,則在寫images的資料表會遇見問題,就是它的外鍵被限制在一個資料表上(users),所以當需要另外表示Post擁有一個Image時,就必須要增加多一份資料表。
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->string('url');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
同一概念卻不能表現在同一張資料表上是造成上面問題的原因,所以透過多紀錄了外鍵的Model型態(imageable_type)為何,讓Image可以自由跟Post或User連結起來。
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->string('url');
$table->unsignedInteger('imageable_id');
$table->string('imageable_type');
$table->timestamps();
});
PS 在activities資料表中所使用的nullableMorphs是Laravel提供的功能,可以幫你產生_id和_tyep的兩個欄位。
來個測試看看是否成功
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
設定Model
class Post extends Model
{
use HasFactory;
public function image()
{
return $this->morphOne('App\Models\Image', 'imageable');
}
}
class Image extends Model
{
protected $fillable =['url'];
use HasFactory;
public function imageable()
{
return $this->morphTo();
}
}
class PolymorphicTest extends TestCase
{
use RefreshDatabase, WithFaker;
/** @test */
public function images_can_belong_to_a_user_or_to_a_post()
{
$user = User::factory()->create();
$post = Post::factory()->create();
$user->image()->create(['url' => $this->faker->url]);
$post->image()->create(['url' => $this->faker->url]);
$this->assertDatabaseHas(
'images',
[
'imageable_id' => $user->id,
'imageable_type' => 'App\Models\User'
]
);
$this->assertDatabaseHas(
'images',
[
'imageable_id' => $post->id,
'imageable_type' => 'App\Models\Post'
]
);
$userImage = $user->image;
$postImage = $post->image;
$this->assertTrue($userImage->imageable->is($user));
$this->assertTrue($postImage->imageable->is($post));
}
}
PS 1. alias test='vendor/bin/phpunit --filter'是當我不用套件,用命令列測試單獨測試時使用的,設定後就可以 test images_can_belong_to_a_user_or_to_a_post; 2. 通常建議在單一測試裡面不要太多assert的斷定,但是因為是自己的練習,所以我都寫在一起。
關係對應的整理
前面說過其實一對一/一對多/多對多在非Polymorphic或是Polymorphic的關係下差異不大,只是Polymorphic可以更自由地連結不同的Model,所以就做個對應的小語法整理,詳細參數官方的範例很清楚。
關係 | 一般 | Polymorphic |
---|
一對一|hasOne/belongsTo|morphOne/morphTo
一對多|hasMany/belongsTo|morphMany/morphTo
多對多|belongsToMany/belongsToMany|morphedByMany/morphedToMany
PS 如果多個Tag可以對應多個Post或多個Video時,則Tag中使用morphedByMany規定跟Post或Video的關係,Post或Video用morphedToMany規定跟Tag的關係
參考文件