Полиморфные связи
Комментарии можно оставлять и к статьям, и к видео, и к товарам. Заводить три почти одинаковые таблицы неудобно. Полиморфная связь позволяет одной модели принадлежать моделям разных типов.
Таблица
Вместо post_id в таблице два столбца: идентификатор и тип владельца. Их создаёт метод morphs():
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->morphs('commentable');
$table->timestamps();
});Появятся столбцы commentable_id и commentable_type.
Модели
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Comment extends Model
{
protected $fillable = ['body'];
public function commentable(): MorphTo
{
return $this->morphTo();
}
}use Illuminate\Database\Eloquent\Relations\MorphMany;
class Post extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}Использование
$post->comments()->create(['body' => 'Отличная статья!']);
$video->comments()->create(['body' => 'Спасибо за видео']);
foreach ($post->comments as $comment) {
echo $comment->body;
}
$owner = $comment->commentable; // Post или VideoLaravel сам заполняет тип и идентификатор и возвращает модель нужного класса.
Одна запись
Для связи «один к одному» — например, одна картинка у пользователя и у товара — есть morphOne():
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}Многие ко многим
Метки подходят и к статьям, и к видео. Связи хранятся в одной промежуточной таблице taggables со столбцами tag_id, taggable_id, taggable_type:
class Post extends Model
{
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
class Tag extends Model
{
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
}$post->tags()->attach($tagId);
$post->tags()->sync([1, 2, 3]);Загрузка без лишних запросов
$comments = Comment::with('commentable')->latest()->get();
$posts = Post::withCount('comments')->get();Карта типов
По умолчанию в столбце типа хранится полное имя класса — App\Models\Post. Если класс переименовать, старые записи потеряют владельца. Карта типов заменяет имена короткими метками:
use Illuminate\Database\Eloquent\Relations\Relation;
public function boot(): void
{
Relation::enforceMorphMap([
'post' => Post::class,
'video' => Video::class,
]);
}Частая ошибка
Для полиморфной связи нельзя создать внешний ключ: столбец commentable_id ссылается на разные таблицы. База не удалит комментарии вместе со статьёй и не помешает сослаться на несуществующую запись. Удаляйте зависимые записи сами — в событии модели deleting — и задайте карту типов с самого начала проекта.