Laravel: События Модели
Я буду использовать один и тот же пример для каждого подхода, чтобы вы могли увидеть прямое сравнение. В этом примере UUID свойство модели будет присвоено UUID во время создания модели.
Первый подход использует метод статической загрузки модели для регистрации поведения. Это позволяет работать непосредственно с моделью и регистрировать обратный вызов, который мы хотим запустить при создании модели.
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Office extends Model
{
public static function boot(): void
{
static::creating(fn (Model $model) =>
$model->uuid = Str::uuid(),
);
}
}
Этот подход идеально подходит для небольших и простых реакций на события модели, таких как добавление UUID, поскольку его легко понять, и можно точно видеть, что происходит в модели. Самая большая проблема с этим подходом — повторение кода, и если у вас есть несколько моделей, которым нужно назначить UUID, вы будете делать одно и то же повторно.
Это прекрасно подводит нас ко второму подходу использующему трейт. В Laravel модели могут наследовать трейты и автоматически загружать их, если вы создадите метод в трейте начинающийся с boot
и заканчивающийся именем трейта. Вот пример:
declare(strict_types=1);
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
trait HasUuid
{
public static function bootHasUuid(): void
{
static::creating(fn (Model $model) =>
$model->uuid = Str::uuid(),
);
}
}
Использование трейта позволяет добавить это поведение к каждой модели, которая его требует и его легко реализовать. Самый существенный недостаток заключается в том, что объединение этих поведений может вызвать проблемы, когда несколько трейтов хотят подключиться к одному и тому же событию модели. Они начинают бороться за приоритет и могут довольно быстро запутаться.
Это приводит нас к следующему варианту, Model Observers — Наблюдатели Модели. Наблюдатели Модели — основанный на классах подход к реагированию на события модели, где методы соответствуют конкретным запускаемым событиям.
declare(strict_types=1);
namespace App\Observers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class OfficeObserver
{
public function creating(Model $model): void
{
$model->uuid = Str::uuid();
}
}
Этот класс нужно будет где-то зарегистрировать, в Сервис Провайдере или в самой модели (где я и рекомендую это сделать). Регистрация этого наблюдателя в модели обеспечивает видимость на уровне модели побочных эффектов изменяющих поведение Eloquent. Проблема с сокрытием наблюдателя в Сервис Провайдере заключается в том, что если не все знаю, что он там — об этом сложно узнать. На мой взгляд, этот подход является фантастическим при правильном использовании.
Ещё один способ решить эту проблему — воспользоваться свойством $dispatchesEvents
в самой Модели Eloquent. Это свойство каждой Модели Eloquent позволяет перечислить события, которые вы хотите прослушивать, и класс вызываемый для этих событий.
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Office extends Model
{
protected $dispatchesEvents = [
'creating' => SetModelUuid::class,
];
}
SetModelUuid
будет создан в течении жизненного цикла модели Eloquent, и это ваш шанс добавить в модель поведение и свойство.
declare(strict_types=1);
namespace App\Models\Events;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class SetModelUuid
{
public function __construct(Model $model)
{
$model->uuid = Str::uuid();
}
}
Этот подход один из самых чистых и простых для понимания, так как в модели достаточно наглядности, и вы легко можете использовать этот класс в разных моделях. Самая большая проблема, с которой вы столкнётесь — это если вам нужно инициализировать несколько действий для события модели.
В заключение, честно говоря, нет правильного способа сделать это. Вы можете выбрать любой из вышеперечисленных методов, и они будут работать, но вам следует выбрать тот, который подходит именно вам и вашему конкретному варианту использования. Я хотел бы видеть больше вариантов для этой конкретной функции.
Например, наблюдатель — хороший вариант, если вам нужно добавить в модель несколько свойств для событий модели. Однако лучший ли это вариант? Как насчёт того, чтобы использовать свойство событий для запуска пользовательского Пайплайна для этой модели?
declare(strict_types=1);
namespace App\Models\Pipelines;
use App\Models\Office
class OfficeCreatingPipeline
{
public function __construct(Office $model)
{
app(Pipeline::class)
->send($model)
->through([
ApplyUuidProperty::class,
TapCreatedBy::class,
]);
}
}
Как видите, мы можем начать использовать Пайплайны для добавления нескольких вариантов поведения к событиям модели. Это не тестировалось, поэтому я не знаю на 100%, будет ли это работать, но в качестве концепции это может открыть компонуемый подход к реагированию на события модели.
А как вы обрабатываете события моделей в своих Laravel проектах?