---
title: 'Laravel relationship tricks'
url: 'https://graker.ru/news/2019/07/03/laravel-relationship-tricks'
language: ru
type: post
author: 'Роман Грачев'
published: '2019-07-03T11:15:00+03:00'
updated: '2019-07-04T09:55:10+03:00'
categories:
  - Laravel
  - Программирование
---

# Laravel relationship tricks

[Отношения](https://laravel.com/docs/5.8/eloquent-relationships) в Laravel в принципе довольно удобно сделаны и работать с ними приятно. Но бывают ситуации, решения для которых находятся в поиске и доках не сразу. Приведу пару небольших примерчиков.

#### Метод morphWith

Конкретно этот метод есть в доках, но он [появился](https://laravel-news.com/laravel-5%E2%80%938%E2%80%9322) совсем недавно, в версии 5.8.22 (а текущая — 5.8.25) и не сразу нашёлся именно поэтому. Суть вот в чем. Допустим, у нас есть модель `Event`, которая радостно полиморфирует с другими моделями. Для неё отношения описаны так:

```
class Event extends Model
{
    protected $with = ['parent'];

    public function parent() {
        return $this->morphTo();
    }

    // ...
}
```

Как мы видим, благодаря свойству `$with` модель будет сразу загружаться с родителем, независимо от того, что это за родитель. Однако что, если нам нужно разом загрузить и какие-то отношения этого (неопределенного пока) родителя с другими моделями. Допустим, модели `Payment` и `Deadline` связаны полиморфно с `Event`, и в то же время модель `Payment` связана с моделью `Order`, а `Deadline` — скажем, с моделью `User`. Примерно так:

```
class Payment extends Model
{
    public function events() {
        return $this->morphMany('App\Event', 'parent');
    }

    public function order() {
        return $this->belongsTo(Order::class);
    }

    // ...
}

// (в другом, конечно, файле)

class Deadline extends Model
{
    public function events() {
        return $this->morphMany('App\Event', 'parent');
    }

    public function User() {
        return $this->belongsTo(User::class);
    }

    // ...
}
```

И мы хотим, чтобы платежи грузились с заказами, а дедлайны — с пользователями. Конечно, мы могли бы воспользоваться свойством `$with` для моделей `Payment` и `Deadline`. Но что если в `Order` и `User` уже используется свойство `$with` для загрузки платежей и дедлайнов? Тогда мы не сможем воспользоваться этим свойством, поскольку уйдем в рекурсию.

Тут-то и поможет нам метод `morphWith()`:

```
$events = Event::query()
            ->with(['parent' => function (MorphTo $morphTo) {
                $morphTo->morphWith([
                    Payment::class => ['order'],
                    Deadline::class => ['user'],
                ]);
            }])
            ->get();
```

То есть что мы тут делаем? Мы вызываем для отношения `MorphTo` метод `morphWith()` и для каждого класса родителя, для которого нужно грузить его отношения, указываем массив из этих отношений. И они грузятся.

#### А что если надо отменить eager loading

Может возникнуть и прямо противоположная проблема. Допустим, мы автоматически грузим модель `Payment` из `Order` через свойство `$with` — это будет работать всякий раз, когда модель заказа загружается. Но что если нам в данном конкретном случае нужно автоматически загрузить заказы для группы платежей, и не попасть при этом ни в рекурсию, ни в «проблему N+1 запросов»?

Очевидно, нужно отключить eager-loading. Вот два примера, как можно это сделать.

1\) Если может возникнуть рекурсия из-за `$with`, можно отключить автозагрузку прямо в объявлении отношения:

```
public function order() {
    return $this->belongsTo(Order::class)->setEagerLoads([]);
}
```

Здесь мы говорим, что при загрузке заказов для данных моделей, не нужно автоматически грузить никакие модели, связанные с заказами (и перечисленные в `Order::$with`). Кстати, вместо пустого массива мы можем передать массив из отношений, которые всё же нужно загрузить, ограничив таким образом автозагрузку заданным списком.

2\) Если же мы прогружаем отношения явным образом с помощью метода `load()`, можно отключить автозагрузку вот так:

```
$payment->load(['order' => function ($query) {
    $query->setEagerLoads([]);
}]);
```

Напоследок замечу вот еще что. Хотя свойство `$with` и довольно удобно в целом, так как работает автоматически, бездумное его использование представляется частным случаем преждевременной оптимизации. Когда мы не знаем, как часто и какие зависимости нам потребуются, велик соблазн пихнуть их все в массив `$with` и забыть. Однако позже, при описании уже реальных кейсов, вполне может возникнуть ситуация, когда отношения, перечисленные в автозагрузке придётся удалять и переделывать одно за другим.

Не нужно, в общем, торопиться с оптимизацией.
