Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Полиморфные связи

Комментарии можно оставлять и к статьям, и к видео, и к товарам. Заводить три почти одинаковые таблицы неудобно. Полиморфная связь позволяет одной модели принадлежать моделям разных типов.

Таблица

Вместо 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 или Video

Laravel сам заполняет тип и идентификатор и возвращает модель нужного класса.

Одна запись

Для связи «один к одному» — например, одна картинка у пользователя и у товара — есть 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 — и задайте карту типов с самого начала проекта.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.