1. Eloquent ORM
  2. Eloquent: Начало работы

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

Laravel включает Eloquent, объектно-реляционный маппер (ORM), который делает взаимодействие с базой данных приятным. При использовании Eloquent каждая таблица базы данных имеет соответствующую "модель", которая используется для взаимодействия с этой таблицей. Помимо извлечения записей из таблицы базы данных, модели Eloquent позволяют вам вставлять, обновлять и удалять записи из таблицы.

Примечание Прежде чем начать, убедитесь, что в вашем файле конфигурации config/database.php вашего приложения настроено подключение к базе данных. Дополнительную информацию о настройке вашей базы данных можно найти в документации по конфигурации базы данных.

Курс по Laravel

Если вы новичок в Laravel, не стесняйтесь приступить к Laravel Bootcamp. Laravel Bootcamp проведет вас через создание вашего первого приложения Laravel с использованием Eloquent. Это отличный способ получить обзор всего, что может предложить Laravel и Eloquent.

Генерация классов моделей

Для начала создадим модель Eloquent. Модели обычно находятся в каталоге app\Models и расширяют класс Illuminate\Database\Eloquent\Model. Вы можете использовать команду Artisan make:model, чтобы создать новую модель:

php artisan make:model Flight

Если вы хотите сгенерировать миграцию базы данных при создании модели, вы можете использовать опцию --migration или -m:

php artisan make:model Flight --migration

Вы можете генерировать различные другие типы классов при создании модели, такие как фабрики, сидеры, политики, контроллеры и формные запросы. Кроме того, эти опции могут быть объединены для создания нескольких классов сразу:

# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f
 
# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s
 
# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c
 
# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
 
# Generate a model and a FlightPolicy class...
php artisan make:model Flight --policy
 
# Generate a model and a migration, factory, seeder, and controller...
php artisan make:model Flight -mfsc
 
# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
php artisan make:model Flight --all
 
# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p

Инспекция моделей

Иногда бывает сложно определить все доступные атрибуты и отношения модели, просто просматривая ее код. Вместо этого попробуйте команду Artisan model:show, которая предоставляет удобный обзор всех атрибутов и отношений модели:

php artisan model:show Flight

Конвенции модели Eloquent

Модели, созданные с помощью команды make:model, будут помещены в каталог app/Models. Давайте рассмотрим базовый класс модели и обсудим некоторые ключевые конвенции Eloquent:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
// ...
}

Имена таблиц

Посмотрев на приведенный выше пример, вы, возможно, заметили, что мы не сказали Eloquent, какая таблица базы данных соответствует нашей модели Flight. По соглашению, в качестве имени таблицы будет использоваться "змеиное написание", множественное имя класса, если явно не указано другое имя. Таким образом, в данном случае Eloquent предположит, что модель Flight хранит записи в таблице flights, а модель AirTrafficController хранит записи в таблице air_traffic_controllers.

Если таблица базы данных, соответствующая вашей модели, не соответствует этому соглашению, вы можете вручную указать имя таблицы модели, определив свойство table в модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Таблица, связанная с моделью.
*
* @var string
*/
protected $table = 'my_flights';
}

Первичные ключи

Eloquent также предполагает, что у каждой модели есть соответствующая таблица базы данных с первичным ключом с именем id. При необходимости вы можете определить защищенное свойство $primaryKey в вашей модели, чтобы указать другой столбец в качестве первичного ключа вашей модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Первичный ключ, связанный с таблицей.
*
* @var string
*/
protected $primaryKey = 'flight_id';
}

Кроме того, Eloquent предполагает, что первичный ключ является инкрементируемым целочисленным значением, что означает, что Eloquent автоматически приведет первичный ключ к целому числу. Если вы хотите использовать первичный ключ, не являющийся инкрементируемым или не числовым, вы должны определить защищенное свойство $incrementing в вашей модели и установить его в false:

<?php
 
class Flight extends Model
{
/**
* Указывает, что ID модели является автоинкрементным.
*
* @var bool
*/
public $incrementing = false;
}

Если первичный ключ вашей модели не является целым числом, вы должны определить защищенное свойство $keyType в вашей модели. Это свойство должно иметь значение string:

<?php
 
class Flight extends Model
{
/**
* Тип данных автоинкрементного ID.
*
* @var string
*/
protected $keyType = 'string';
}

Составные ключи

Eloquent требует, чтобы у каждой модели был как минимум один уникальный идентификационный "ID", который может служить ее первичным ключом. "Композитные" первичные ключи не поддерживаются моделями Eloquent. Тем не менее вы можете добавлять дополнительные многоколоночные уникальные индексы в ваши таблицы базы данных, в дополнение к уникальному первичному ключу таблицы.

Ключи UUID и ULID

Вместо использования инкрементируемых целых чисел в качестве первичных ключей вашей модели Eloquent вы можете выбрать вместо этого использовать UUID. UUID - это уникальные универсальные альфа-числовые идентификаторы, длиной 36 символов.

Если вы хотите, чтобы модель использовала ключ UUID вместо инкрементируемого целочисленного ключа, вы можете использовать трейт Illuminate\Database\Eloquent\Concerns\HasUuids в модели. Конечно же, вы должны убедиться, что у модели есть столбец первичного ключа с эквивалентом UUID:

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
 
class Article extends Model
{
use HasUuids;
 
// ...
}
 
$article = Article::create(['title' => 'Traveling to Europe']);
 
$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"

По умолчанию трейт HasUuids будет генерировать упорядоченные UUID для ваших моделей. Эти UUID более эффективны для хранения в индексированной базе данных, потому что их можно лексикографически сортировать.

Вы можете переопределить процесс генерации UUID для данной модели, определив метод newUniqueId в модели. Кроме того, вы можете указать, для каких столбцов следует использовать UUID, определив метод uniqueIds в модели:

use Ramsey\Uuid\Uuid;
 
/**
* Создать новый UUID для модели.
*/
public function newUniqueId(): string
{
return (string) Uuid::uuid4();
}
 
/**
* Получить столбцы, которые должны получить уникальный идентификатор.
*
* @return array<int, string>
*/
public function uniqueIds(): array
{
return ['id', 'discount_code'];
}

Если вы хотите, вы можете выбрать использовать вместо UUID "ULID". ULID похожи на UUID, однако они имеют длину всего 26 символов. Как и упорядоченные UUID, ULID лексикографически сортируются для эффективного индексирования базы данных. Чтобы использовать ULID, вы должны использовать трейт Illuminate\Database\Eloquent\Concerns\HasUlids в вашей модели. Вы также должны убедиться, что у модели есть столбец первичного ключа с эквивалентом ULID:

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;
 
class Article extends Model
{
use HasUlids;
 
// ...
}
 
$article = Article::create(['title' => 'Traveling to Asia']);
 
$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"

Отметки времени

По умолчанию Eloquent ожидает, что столбцы created_at и updated_at существуют в таблице базы данных, соответствующей вашей модели. Eloquent автоматически устанавливает значения этих столбцов при создании или обновлении моделей. Если вы не хотите, чтобы эти столбцы автоматически управлялись Eloquent, вы должны определить свойство $timestamps на вашей модели со значением false:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Указывает, должна ли модель содержать отметки времени.
*
* @var bool
*/
public $timestamps = false;
}

Если вам нужно настроить формат временных меток вашей модели, установите свойство $dateFormat на вашей модели. Это свойство определяет, как хранятся атрибуты даты в базе данных, а также их формат при сериализации модели в массив или JSON:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Формат хранения датовых столбцов модели.
*
* @var string
*/
protected $dateFormat = 'U';
}

Если вам нужно настроить имена столбцов, используемых для хранения временных меток, вы можете определить константы CREATED_AT и UPDATED_AT на вашей модели:

<?php
 
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}

Если вы хотите выполнять операции с моделью без изменения временной метки updated_at, вы можете выполнять операции с моделью в пределах замыкания, переданного методу withoutTimestamps:

Model::withoutTimestamps(fn () => $post->increment(['reads']));

Подключения к базе данных

По умолчанию все модели Eloquent будут использовать соединение с базой данных, настроенное для вашего приложения. Если вы хотите указать другое соединение, которое следует использовать при взаимодействии с конкретной моделью, вы должны определить свойство $connection на модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Соединение с базой данных, которое должно использоваться моделью.
*
* @var string
*/
protected $connection = 'sqlite';
}

Значения атрибутов по умолчанию

По умолчанию новый экземпляр модели не будет содержать значений атрибутов. Если вы хотите задать значения по умолчанию для некоторых атрибутов вашей модели, вы можете определить свойство $attributes на вашей модели. Значения атрибутов, помещенные в массив $attributes, должны быть в их "сыром", "хранящемся" формате, как если бы они только что были считаны из базы данных:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Значения атрибутов модели по умолчанию.
*
* @var array
*/
protected $attributes = [
'options' => '[]',
'delayed' => false,
];
}

Настройка строгости Eloquent

Laravel предлагает несколько методов, которые позволяют настраивать поведение Eloquent и "строгость" в различных ситуациях.

Во-первых, метод preventLazyLoading принимает необязательный логический аргумент, указывающий, следует ли предотвращать отложенную загрузку. Например, вы можете захотеть отключить отложенную загрузку только в средах, отличных от производственной, чтобы ваша производственная среда продолжала работать нормально, даже если отложенная загрузка отношений случайно присутствует в коде для продакшена. Обычно этот метод следует вызывать в методе boot сервис-провайдера вашего приложения AppServiceProvider:

use Illuminate\Database\Eloquent\Model;
 
/**
* Запуск любых служб приложения.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}

Также вы можете указать Laravel бросать исключение при попытке заполнения атрибута, который не был добавлен в массив fillable, вызвав метод preventSilentlyDiscardingAttributes. Это может помочь предотвратить неожиданные ошибки во время локальной разработки при попытке установить атрибут, который не был добавлен в массив fillable модели:

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

Извлечение моделей

После создания модели и связанной с ней таблицы базы данных вы готовы начать извлекать данные из базы данных. Вы можете рассматривать каждую модель Eloquent как мощный строитель запросов, позволяющий вам легко выполнять запросы к таблице базы данных, связанной с моделью. Метод all модели извлечет все записи из связанной с моделью таблицы базы данных:

use App\Models\Flight;
 
foreach (Flight::all() as $flight) {
echo $flight->name;
}

Построение запросов

Метод Eloquent all вернет все результаты из таблицы модели. Однако, поскольку каждая модель Eloquent служит строителем запросов, вы можете добавлять дополнительные ограничения к запросам и затем вызывать метод get для получения результатов:

$flights = Flight::where('active', 1)
->orderBy('name')
->take(10)
->get();

Примечание Поскольку модели Eloquent являются построителями запросов, вы должны изучить все методы, предоставляемые построителем запросов Laravel. Вы можете использовать любой из этих методов при написании запросов Eloquent.

Обновление моделей

Если у вас уже есть экземпляр модели Eloquent, полученный из базы данных, вы можете "обновить" модель, используя методы fresh и refresh. Метод fresh повторно извлечет модель из базы данных. Существующий экземпляр модели не будет затронут:

$flight = Flight::where('number', 'FR 900')->first();
 
$freshFlight = $flight->fresh();

Метод refresh повторно гидрирует существующую модель, используя свежие данные из базы данных. Кроме того, все ее загруженные отношения также будут обновлены:

$flight = Flight::where('number', 'FR 900')->first();
 
$flight->number = 'FR 456';
 
$flight->refresh();
 
$flight->number; // "FR 900"

Коллекции

Как мы видели, методы Eloquent, такие как all и get, извлекают несколько записей из базы данных. Однако эти методы не возвращают простой массив PHP. Вместо этого возвращается экземпляр класса Illuminate\Database\Eloquent\Collection.

Класс коллекции Eloquent расширяет базовый класс коллекций Laravel Illuminate\Support\Collection, который предоставляет различные полезные методы для взаимодействия с коллекциями данных. Например, метод reject может использоваться для удаления моделей из коллекции на основе результатов вызванного замыкания:

$flights = Flight::where('destination', 'Paris')->get();
 
$flights = $flights->reject(function (Flight $flight) {
return $flight->cancelled;
});

Помимо методов, предоставляемых базовым классом коллекций Laravel, класс коллекций Eloquent предоставляет несколько дополнительных методов, которые предназначены специально для взаимодействия с коллекциями моделей Eloquent.

Поскольку все коллекции Laravel реализуют интерфейсы итераторов PHP, вы можете перебирать коллекции, как если бы они были массивом:

foreach ($flights as $flight) {
echo $flight->name;
}

Пакетная обработка результатов

Ваше приложение может исчерпать память, если вы попытаетесь загрузить десятки тысяч записей Eloquent с помощью методов all или get. Вместо использования этих методов можно использовать метод chunk для более эффективной обработки большого количества моделей.

Метод chunk извлекает подмножество моделей Eloquent, передает их в замыкание для обработки. Поскольку только текущий фрагмент моделей Eloquent извлекается за один раз, метод chunk обеспечивает значительно сниженное использование памяти при работе с большим количеством моделей:

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
 
Flight::chunk(200, function (Collection $flights) {
foreach ($flights as $flight) {
// ...
}
});

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

Если вы фильтруете результаты метода chunk на основе столбца, который вы также собираетесь обновить в процессе итерации по результатам, вы должны использовать метод chunkById. Использование метода chunk в этих сценариях может привести к неожиданным и несогласованным результатам. Внутренне метод chunkById всегда будет извлекать модели с id столбцом больше, чем у последней модели в предыдущем фрагменте:

Flight::where('departed', true)
->chunkById(200, function (Collection $flights) {
$flights->each->update(['departed' => false]);
}, $column = 'id');

Пакетная обработка с использованием ленивых коллекций

Метод lazy работает аналогично методу chunk в том смысле, что за кулисами он выполняет запрос порциями. Однако вместо того чтобы передавать каждый фрагмент напрямую в обратный вызов, метод lazy возвращает выровненную LazyCollection моделей Eloquent, которая позволяет вам взаимодействовать с результатами как с одним потоком:

use App\Models\Flight;
 
foreach (Flight::lazy() as $flight) {
// ...
}

Если вы фильтруете результаты метода lazy на основе столбца, который вы также собираетесь обновить в процессе итерации по результатам, вы должны использовать метод lazyById. Внутренне метод lazyById всегда будет извлекать модели с id столбцом больше, чем у последней модели в предыдущем фрагменте:

Flight::where('departed', true)
->lazyById(200, $column = 'id')
->each->update(['departed' => false]);

Вы можете фильтровать результаты на основе убывающего порядка id с использованием метода lazyByIdDesc.

Курсоры

Аналогично методу lazy, метод cursor можно использовать для значительного уменьшения потребления памяти вашим приложением при итерации по десяткам тысяч записей моделей Eloquent.

Метод cursor выполнит только один запрос к базе данных; однако модели Eloquent не будут гидрированы, пока не будут фактически перебраны. Таким образом, при итерации по курсору в памяти будет храниться только одна модель Eloquent.

Внимание Поскольку метод cursor хранит в памяти всегда только одну модель Eloquent, он не может жадно загружать отношения. Если вам нужно жадно загружать отношения, рассмотрите возможность использования метода lazy вместо этого.

Внутренне метод cursor использует генераторы PHP для реализации этой функциональности:

use App\Models\Flight;
 
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
// ...
}

Метод cursor возвращает экземпляр Illuminate\Support\LazyCollection. Ленивые коллекции позволяют использовать множество методов коллекций, доступных в типичных коллекциях Laravel, при этом загружая в память только одну модель.

use App\Models\User;
 
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
 
foreach ($users as $user) {
echo $user->id;
}

Хотя метод cursor использует гораздо меньше памяти, чем обычный запрос (загружая только одну модель Eloquent в память за один раз), он все равно может когда-то исчерпать память. Это обусловлено тем, что внутренне драйвер PDO PHP кэширует все результаты сырых запросов в своем буфере. Если у вас большое количество записей Eloquent, рассмотрите возможность использования метода lazy вместо этого.

Расширенные подзапросы

Подзапросы Select

Eloquent также предлагает расширенную поддержку подзапросов, позволяющую извлекать информацию из связанных таблиц в одном запросе. Например, представим, что у нас есть таблица мест назначения destinations и таблица flights к местам назначения. Таблица flights содержит столбец arrived_at, который указывает, когда рейс прибыл в пункт назначения.

Используя функциональность подзапросов, доступную методам select и addSelect построителя запросов, мы можем выбрать все destinations и название рейса, который недавно прибыл в каждое место назначения, с использованием одного запроса:

use App\Models\Destination;
use App\Models\Flight;
 
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
])->get();

Упорядочение подзапросов

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

return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
)->get();

Извлечение одиночных моделей / Агрегатов

Помимо извлечения всех записей, соответствующих заданному запросу, вы также можете извлекать отдельные записи с использованием методов find, first или firstWhere. Вместо возвращения коллекции моделей эти методы возвращают один экземпляр модели:

use App\Models\Flight;
 
// Получить модель по ее первичному ключу...
$flight = Flight::find(1);
 
// Получить первую модель, соответствующую условиям запроса...
$flight = Flight::where('active', 1)->first();
 
// Альтернатива получения первой модели, соответствующей условиям запроса...
$flight = Flight::firstWhere('active', 1);

Иногда вам может захотеться выполнить какое-то другое действие, если результаты не найдены. Методы findOr и firstOr вернут один экземпляр модели или, если результаты не найдены, выполнит заданное замыкание. Значение, возвращенное замыканием, будет считаться результатом метода:

$flight = Flight::findOr(1, function () {
// ...
});
 
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
});

Исключения "Не найдено"

Иногда вы можете захотеть вызвать исключение, если модель не найдена. Это особенно полезно в маршрутах или контроллерах. Методы findOrFail и firstOrFail извлекут первый результат запроса; однако, если результат не найден, будет вызвано исключение Illuminate\Database\Eloquent\ModelNotFoundException:

$flight = Flight::findOrFail(1);
 
$flight = Flight::where('legs', '>', 3)->firstOrFail();

Если исключение ModelNotFoundException не обрабатывается, автоматически отправляется ответ HTTP 404 клиенту:

use App\Models\Flight;
 
Route::get('/api/flights/{id}', function (string $id) {
return Flight::findOrFail($id);
});

Извлечение или создание моделей

Метод firstOrCreate попытается найти запись в базе данных, используя указанные пары столбец / значение. Если модель не может быть найдена в базе данных, будет вставлена запись с атрибутами, полученными путем объединения первого массива аргументов с необязательным вторым массивом аргументов:

Метод firstOrNew, как и firstOrCreate, попытается найти запись в базе данных, соответствующую указанным атрибутам. Однако, если модель не найдена, будет возвращен новый экземпляр модели. Обратите внимание, что модель, возвращаемая методом firstOrNew, еще не была сохранена в базе данных. Вам нужно будет вручную вызвать метод save, чтобы сохранить ее:

use App\Models\Flight;
 
// Получить рейс по имени или создать его, если его нет...
$flight = Flight::firstOrCreate([
'name' => 'London to Paris'
]);
 
// Получить рейс по имени или создать его с атрибутами имени, задержки и времени прибытия...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);
 
// Получить рейс по имени или создать новый экземпляр Flight...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);
 
// Получить рейс по имени или создать его с атрибутами имени, задержки и времени прибытия...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);

Извлечение агрегатов

При взаимодействии с моделями Eloquent можно использовать также методы count, sum, max и другие агрегатные методы, предоставляемые строителем запросов Laravel. Как и следовало ожидать, эти методы возвращают скалярное значение, а не экземпляр модели Eloquent:

$count = Flight::where('active', 1)->count();
 
$max = Flight::where('active', 1)->max('price');

Вставка и обновление моделей

Вставки

Конечно же, при использовании Eloquent нам необходимо не только извлекать модели из базы данных. Нам также нужно вставлять новые записи. К счастью, Eloquent делает это простым. Чтобы вставить новую запись в базу данных, вы должны создать новый экземпляр модели и установить атрибуты модели. Затем вызовите метод save для экземпляра модели:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class FlightController extends Controller
{
/**
* Сохранить новый рейс в базе данных.
*/
public function store(Request $request): RedirectResponse
{
// Проверить запрос...
 
$flight = new Flight;
 
$flight->name = $request->name;
 
$flight->save();
 
return redirect('/flights');
}
}

В этом примере мы присваиваем поле name из входящего HTTP-запроса атрибуту name экземпляра модели App\Models\Flight. Когда мы вызываем метод save, запись будет вставлена в базу данных. Метки времени created_at и updated_at модели автоматически установятся, когда будет вызван метод save, поэтому нет необходимости устанавливать их вручную.

В качестве альтернативы вы можете использовать метод create для "сохранения" новой модели с использованием единственного оператора PHP. Вставленный экземпляр модели вернется вам методом create:

use App\Models\Flight;
 
$flight = Flight::create([
'name' => 'London to Paris',
]);

Однако перед использованием метода create вам нужно будет указать свойство fillable или guarded на вашем классе модели. Эти свойства необходимы, потому что все модели Eloquent по умолчанию защищены от уязвимостей массового присвоения. Чтобы узнать больше о массовом присвоении, пожалуйста, ознакомьтесь с документацией по массовому присвоению.

Обновления

Метод save также можно использовать для обновления моделей, которые уже существуют в базе данных. Чтобы обновить модель, вы должны извлечь ее и установить любые атрибуты, которые вы хотите обновить. Затем вы должны вызвать метод save модели. Опять же, метка времени updated_at будет автоматически обновлена, поэтому нет необходимости устанавливать ее значение вручную:

use App\Models\Flight;
 
$flight = Flight::find(1);
 
$flight->name = 'Paris to London';
 
$flight->save();

Массовые обновления

Обновления также могут выполняться для моделей, которые соответствуют данному запросу. В этом примере все рейсы, которые активны и имеют пункт назначения San Diego, будут отмечены как задержанные:

Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);

Метод update ожидает массив пар столбец и значение, представляющих собой столбцы, которые должны быть обновлены. Метод update возвращает количество затронутых строк.

Внимание При выполнении массового обновления через Eloquent события модели saving, saved, updating и updated не будут вызваны для обновленных моделей. Это происходит потому, что модели фактически никогда не извлекаются при выполнении массового обновления.

Изучение изменений атрибутов

Eloquent предоставляет методы isDirty, isClean и wasChanged для изучения внутреннего состояния вашей модели и определения того, как ее атрибуты изменились с того момента, когда модель была изначально получена.

Метод isDirty определяет, были ли изменены какие-либо атрибуты модели с момента получения модели. Вы можете передать конкретное имя атрибута или массив атрибутов методу isDirty, чтобы определить, являются ли какие-либо из атрибутов "грязными". Метод isClean определит, остался ли атрибут неизменным с момента получения модели. Этот метод также принимает необязательный аргумент для атрибута:

use App\Models\User;
 
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
 
$user->title = 'Painter';
 
$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'title']); // true
 
$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->isClean(['first_name', 'title']); // false
 
$user->save();
 
$user->isDirty(); // false
$user->isClean(); // true

Метод wasChanged определяет, были ли изменены какие-либо атрибуты, когда модель была последний раз сохранена в текущем цикле запроса. При необходимости вы можете передать имя атрибута, чтобы узнать, был ли изменен конкретный атрибут:

$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
 
$user->title = 'Painter';
 
$user->save();
 
$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged(['title', 'slug']); // true
$user->wasChanged('first_name'); // false
$user->wasChanged(['first_name', 'title']); // true

Метод getOriginal возвращает массив, содержащий исходные атрибуты модели независимо от любых изменений модели с момента ее получения. При необходимости вы можете передать конкретное имя атрибута, чтобы получить исходное значение конкретного атрибута:

$user = User::find(1);
 
$user->name; // John
$user->email; // [email protected]
 
$user->name = "Jack";
$user->name; // Jack
 
$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...

Массовое присваивание

Вы можете использовать метод create для "сохранения" новой модели с использованием единственного оператора PHP. Вставленный экземпляр модели будет возвращен вам методом:

use App\Models\Flight;
 
$flight = Flight::create([
'name' => 'London to Paris',
]);

Однако перед использованием метода create вам нужно будет указать свойство fillable или guarded в вашем классе модели. Эти свойства необходимы, потому что все модели Eloquent по умолчанию защищены от уязвимостей массового присвоения.

Уязвимость массового присвоения возникает, когда пользователь передает неожиданное поле HTTP-запроса, и это поле изменяет столбец в вашей базе данных, которого вы не ожидали. Например, злонамеренный пользователь может отправить параметр is_admin через HTTP-запрос, который затем передается методу create вашей модели, позволяя пользователю повысить себя до администратора.

Так что, чтобы начать, вы должны определить, какие атрибуты модели вы хотите сделать массовым присваиванием. Вы можете сделать это, используя свойство $fillable в модели. Например, давайте сделаем атрибут name нашей модели Flight массово присваиваемым:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
/**
* Атрибуты, которые могут массово присваиваться.
*
* @var array
*/
protected $fillable = ['name'];
}

После того как вы указали, какие атрибуты можно массово присваивать, вы можете использовать метод create для вставки новой записи в базу данных. Метод create возвращает только что созданный экземпляр модели:

$flight = Flight::create(['name' => 'London to Paris']);

Если у вас уже есть экземпляр модели, вы можете использовать метод fill, чтобы заполнить его массивом атрибутов:

$flight->fill(['name' => 'Amsterdam to Frankfurt']);

Массовое присваивание и столбцы JSON

При присвоении атрибутов JSON каждый ключ массового присваивания каждого столбца должен быть указан в массиве $fillable вашей модели. В целях безопасности Laravel не поддерживает обновление вложенных атрибутов JSON при использовании свойства guarded:

/**
* Атрибуты, которые могут массово присваиваться.
*
* @var array
*/
protected $fillable = [
'options->enabled',
];

Разрешение массового присваивания

Если вы хотите сделать все ваши атрибуты массово присваиваемыми, вы можете определить свойство $guarded модели как пустой массив. Если вы решите разблокировать свою модель, вы должны особенно внимательно подходить к созданию массивов, передаваемых методам fill, create и update Eloquent:

/**
* Атрибуты, которые не могут массово присваиваться.
*
* @var array
*/
protected $guarded = [];

Исключения массового присваивания

По умолчанию атрибуты, которые не включены в массив $fillable, молча отбрасываются при выполнении операций массового присвоения. В производстве это ожидаемое поведение; однако во время локальной разработки это может вызвать путаницу в том, почему изменения модели не вступают в силу.

Если вы хотите, вы можете научить Laravel выбрасывать исключение при попытке присвоить атрибут, который нельзя массово присваивать, вызвав метод preventSilentlyDiscardingAttributes. Обычно этот метод следует вызывать в методе boot одного из сервис-провайдеров вашего приложения:

use Illuminate\Database\Eloquent\Model;
 
/**
* Запуск любых служб приложения.
*/
public function boot(): void
{
Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

Upserts

Иногда вам может потребоваться обновить существующую модель или создать новую модель, если соответствующей модели не существует. Как и метод firstOrCreate, метод updateOrCreate сохраняет модель, поэтому нет необходимости вручную вызывать метод save.

В приведенном ниже примере, если существует рейс с местом departure в Oakland и местом destination в San Diego, его столбцы price и discounted будут обновлены. Если такой рейс не существует, будет создан новый рейс с атрибутами, полученными путем объединения массива первого аргумента с массивом второго аргумента:

$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);

Если вы хотите выполнить несколько "upserts" в одном запросе, вы должны использовать метод upsert вместо этого. Первый аргумент метода состоит из значений для вставки или обновления, в то время как второй аргумент перечисляет столбцы, уникально идентифицирующие записи в связанной таблице. Третий и последний аргумент метода - это массив столбцов, которые должны быть обновлены, если соответствующая запись уже существует в базе данных. Метод upsert автоматически устанавливает метки времени created_at и updated_at, если метки времени включены для модели:

Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);

Внимание Все базы данных, кроме SQL Server, требуют, чтобы столбцы второго аргумента метода upsert имели "первичный" или "уникальный" индекс. Кроме того, драйвер базы данных MySQL игнорирует второй аргумент метода upsert и всегда использует индексы "первичного" и "уникального" ключа таблицы для определения существующих записей.

Удаление моделей

Для удаления модели вы можете вызвать метод delete для экземпляра модели:

use App\Models\Flight;
 
$flight = Flight::find(1);
 
$flight->delete();

Вы можете вызвать метод truncate, чтобы удалить все записи в связанной таблице модели. Операция truncate также сбросит любые автоматически увеличиваемые идентификаторы на связанной таблице модели:

Flight::truncate();

Удаление существующей модели по ее первичному ключу

В приведенном выше примере мы получаем модель из базы данных перед вызовом метода delete. Однако, если вы знаете первичный ключ модели, вы можете удалить модель, не явно извлекая ее, вызвав метод destroy. В дополнение к принятию единичного первичного ключа, метод destroy примет несколько первичных ключей, массив первичных ключей или коллекцию первичных ключей:

Flight::destroy(1);
 
Flight::destroy(1, 2, 3);
 
Flight::destroy([1, 2, 3]);
 
Flight::destroy(collect([1, 2, 3]));

Внимание Метод destroy загружает каждую модель индивидуально и вызывает метод delete, чтобы события модели deleting и deleted правильно отправлялись для каждой модели.

Удаление моделей с использованием запросов

Конечно же, вы можете создать запрос Eloquent для удаления всех моделей, соответствующих критериям вашего запроса. В этом примере мы удалим все рейсы, которые отмечены как неактивные. Как и массовые обновления, массовые удаления не будут отправлять события модели для удаленных моделей:

$deleted = Flight::where('active', 0)->delete();

Внимание При выполнении массового оператора удаления через Eloquent события модели deleting и deleted не будут отправляться для удаленных моделей. Это происходит потому, что модели фактически никогда не извлекаются при выполнении оператора удаления.

Мягкое удаление

Помимо фактического удаления записей из базы данных, Eloquent также может "мягко удалять" модели. Когда модели мягко удаляются, они фактически не удаляются из базы данных. Вместо этого на модели устанавливается атрибут deleted_at, указывающий дату и время, когда модель была "удалена". Чтобы включить мягкое удаление для модели, добавьте трейт Illuminate\Database\Eloquent\SoftDeletes к модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
 
class Flight extends Model
{
use SoftDeletes;
}

Примечание Трейт SoftDeletes автоматически преобразует атрибут deleted_at в экземпляр DateTime / Carbon за вас.

Также добавьте столбец deleted_at в таблицу вашей базы данных. Строитель схем Laravel содержит вспомогательный метод, чтобы создать этот столбец:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
 
Schema::table('flights', function (Blueprint $table) {
$table->softDeletes();
});
 
Schema::table('flights', function (Blueprint $table) {
$table->dropSoftDeletes();
});

Теперь, когда вы вызываете метод delete для модели, столбец deleted_at будет установлен в текущую дату и время. Однако запись модели в базе данных останется в таблице. При запросе модели, использующей мягкое удаление, мягко удаленные модели автоматически будут исключены из всех результатов запроса.

Чтобы определить, была ли данная модель мягко удалена, вы можете использовать метод trashed:

if ($flight->trashed()) {
// ...
}

Восстановление мягко удаленных моделей

Иногда вам может потребоваться "восстановить" мягко удаленную модель. Чтобы восстановить мягко удаленную модель, вы можете вызвать метод restore для экземпляра модели. Метод restore установит столбец deleted_at модели в значение null:

$flight->restore();

Вы также можете использовать метод restore в запросе для восстановления нескольких моделей. Снова, как и в других "массовых" операциях, это не будет отправлять любые события модели для восстановленных моделей:

Flight::withTrashed()
->where('airline_id', 1)
->restore();

Метод restore также может использоваться при построении запросов к отношениям:

$flight->history()->restore();

Полное удаление моделей

Иногда вам может понадобиться действительно удалить модель из вашей базы данных. Вы можете использовать метод forceDelete для окончательного удаления мягко удаленной модели из таблицы базы данных:

$flight->forceDelete();

Вы также можете использовать метод forceDelete при построении запросов отношений Eloquent:

$flight->history()->forceDelete();

Запрос мягко удаленных моделей

Включение мягкого удаления моделей

Как указано выше, мягко удаленные модели автоматически будут исключены из результатов запроса. Однако вы можете принудительно включить мягко удаленные модели в результаты запроса, вызвав метод withTrashed в запросе:

use App\Models\Flight;
 
$flights = Flight::withTrashed()
->where('account_id', 1)
->get();

Метод withTrashed также может быть вызван при построении запроса отношения:

$flight->history()->withTrashed()->get();

Извлечение только мягко удаленных моделей

Метод onlyTrashed извлечет только мягко удаленные модели:

$flights = Flight::onlyTrashed()
->where('airline_id', 1)
->get();

Обрезка моделей

Иногда вам может потребоваться периодически удалять модели, которые больше не нужны. Для этого вы можете добавить трейт Illuminate\Database\Eloquent\Prunable или Illuminate\Database\Eloquent\MassPrunable к моделям, которые вы хотели бы периодически обрезать. После добавления одного из трейтов к модели реализуйте метод prunable, который возвращает построитель запросов Eloquent, разрешающий модели, которые больше не нужны:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
 
class Flight extends Model
{
use Prunable;
 
/**
* Получить запрос модели, которую можно устарить.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}

При пометке моделей как Prunable вы также можете определить метод pruning в модели. Этот метод будет вызван перед удалением модели. Этот метод может быть полезен для удаления любых дополнительных ресурсов, связанных с моделью, таких как хранимые файлы, перед окончательным удалением модели из базы данных:

/**
* Подготовить модель к устареванию.
*/
protected function pruning(): void
{
// ...
}

После настройки вашей обрезаемой модели вы должны планировать команду Artisan model:prune в классе App\Console\Kernel вашего приложения. Вы вольны выбирать подходящий интервал, с которым должна выполняться эта команда:

/**
* Определить расписание команд приложения.
*/
protected function schedule(Schedule $schedule): void
{
$schedule->command('model:prune')->daily();
}

По сути, команда model:prune будет автоматически обнаруживать модели "Prunable" в каталоге вашего приложения app/Models. Если ваши модели находятся в другом месте, вы можете использовать опцию --model, чтобы указать имена классов моделей:

$schedule->command('model:prune', [
'--model' => [Address::class, Flight::class],
])->daily();

Если вы хотите исключить определенные модели из обрезки, при этом подрезать все другие обнаруженные модели, вы можете использовать опцию --except:

$schedule->command('model:prune', [
'--except' => [Address::class, Flight::class],
])->daily();

Вы можете проверить свой запрос prunable, выполнив команду model:prune с опцией --pretend. В режиме имитации команда model:prune просто сообщит, сколько записей было бы подрезано, если бы команда была действительно выполнена:

php artisan model:prune --pretend

Внимание Модели, которые использовали мягкое удаление, будут окончательно удалены (forceDelete), если они соответствуют запросу на удаление.

Массовое устаревание

Когда модели помечены трейтом Illuminate\Database\Eloquent\MassPrunable, они удаляются из базы данных с использованием запросов массового удаления. Следовательно, метод pruning не будет вызван, и события модели deleting и deleted не будут отправляться. Это происходит потому, что модели фактически никогда не извлекаются перед удалением, что делает процесс подрезки гораздо более эффективным:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;
 
class Flight extends Model
{
use MassPrunable;
 
/**
* Получить запрос модели, которую можно устарить.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}

Дублирование моделей

Вы можете создать несохраненную копию существующего экземпляра модели, используя метод replicate. Этот метод особенно полезен, когда у вас есть экземпляры модели, которые имеют много общих атрибутов:

use App\Models\Address;
 
$shipping = Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
 
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
 
$billing->save();

Чтобы исключить один или несколько атрибутов из реплицируемых в новую модель, вы можете передать массив методу replicate:

$flight = Flight::create([
'destination' => 'LAX',
'origin' => 'LHR',
'last_flown' => '2020-03-04 11:00:00',
'last_pilot_id' => 747,
]);
 
$flight = $flight->replicate([
'last_flown',
'last_pilot_id'
]);

Области видимости запросов

Глобальные области видимости

Глобальные области видимости позволяют добавлять ограничения ко всем запросам для данной модели. Собственный механизм мягкого удаления Laravel использует глобальные области видимости, чтобы извлекать только "неудаленные" модели из базы данных. Написание своих глобальных областей видимости может предоставить удобный и простой способ убедиться, что каждый запрос для данной модели получает определенные ограничения.

Генерация областей видимости

Для создания новой глобальной области видимости вы можете вызвать команду Artisan make:scope, которая поместит сгенерированную область видимости в каталог app/Models/Scopes вашего приложения:

php artisan make:scope AncientScope

Написание глобальных областей видимости

Написание глобальной области видимости просто. Сначала используйте команду make:scope, чтобы создать класс, реализующий интерфейс Illuminate\Database\Eloquent\Scope. Интерфейс Scope требует реализации одного метода: apply. Метод apply может добавлять ограничения where или другие типы выражений к запросу по мере необходимости:

<?php
 
namespace App\Models\Scopes;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
 
class AncientScope implements Scope
{
/**
* Применить область видимости к данному построителю запросов Eloquent.
*/
public function apply(Builder $builder, Model $model): void
{
$builder->where('created_at', '<', now()->subYears(2000));
}
}

Примечание Если ваша глобальная область видимости добавляет столбцы в выражение SELECT запроса, вы должны использовать метод addSelect вместо select. Это предотвратит непреднамеренную замену существующего выражения SELECT запроса.

Применение глобальных областей видимости

Чтобы назначить глобальную область видимости модели, вы должны переопределить метод booted модели и вызвать метод addGlobalScope модели. Метод addGlobalScope принимает в качестве единственного аргумента экземпляр вашей области видимости:

<?php
 
namespace App\Models;
 
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Метод "booted" модели.
*/
protected static function booted(): void
{
static::addGlobalScope(new AncientScope);
}
}

После добавления области видимости в приведенном выше примере к модели App\Models\User, вызов метода User::all() выполнит следующий SQL-запрос:

select * from `users` where `created_at` < 0021-02-18 00:00:00

Анонимные глобальные области видимости

Eloquent также позволяет определять глобальные области видимости с использованием замыканий, что особенно полезно для простых областей видимости, которые не заслуживают отдельного класса. При определении глобальной области видимости с использованием замыкания вы должны предоставить имя области видимости по вашему выбору в качестве первого аргумента методу addGlobalScope:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Метод "booted" модели.
*/
protected static function booted(): void
{
static::addGlobalScope('ancient', function (Builder $builder) {
$builder->where('created_at', '<', now()->subYears(2000));
});
}
}

Удаление глобальных областей видимости

Если вы хотите удалить глобальную область видимости для данного запроса, вы можете использовать метод withoutGlobalScope. Этот метод принимает имя класса глобальной области видимости в качестве единственного аргумента:

User::withoutGlobalScope(AncientScope::class)->get();

Или, если вы определили глобальную область видимости с использованием замыкания, вы должны передать строковое имя, которое вы присвоили глобальной области видимости:

User::withoutGlobalScope('ancient')->get();

Если вы хотите удалить несколько или даже все глобальные области видимости запроса, вы можете использовать метод withoutGlobalScopes:

// Удалить все глобальные области видимости...
User::withoutGlobalScopes()->get();
 
// Удалить некоторые глобальные области видимости...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();

Локальные области видимости

Локальные области видимости позволяют определять общие наборы ограничений запросов, которые вы можете легко повторно использовать в вашем приложении. Например, вам может часто потребоваться извлекать всех пользователей, считающихся "популярными". Для определения области видимости добавьте к методу модели Eloquent префикс scope.

Области видимости должны всегда возвращать тот же экземпляр построителя запросов или void:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Ограничить запрос только популярными пользователями.
*/
public function scopePopular(Builder $query): void
{
$query->where('votes', '>', 100);
}
 
/**
* Ограничить запрос только активными пользователями.
*/
public function scopeActive(Builder $query): void
{
$query->where('active', 1);
}
}

Использование локальной области видимости

После того как область видимости была определена, вы можете вызывать методы области видимости при запросе модели. Однако при вызове метода вы не должны включать префикс scope. Вы можете даже объединять вызовы различных областей видимости:

use App\Models\User;
 
$users = User::popular()->active()->orderBy('created_at')->get();

Совмещение нескольких областей видимости модели Eloquent с использованием оператора запроса or может потребовать использования замыканий для достижения правильной логической группировки:

$users = User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get();

Однако, поскольку это может быть неудобно, Laravel предоставляет метод orWhere "более высокого порядка", который позволяет вам свободно объединять области видимости без использования замыканий:

$users = User::popular()->orWhere->active()->get();

Динамические области видимости

Иногда вам может потребоваться определить область видимости, принимающую параметры. Чтобы начать, просто добавьте свои дополнительные параметры в сигнатуру метода вашей области видимости. Параметры области видимости должны быть определены после параметра $query:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Ограничить запрос только пользователями заданного типа.
*/
public function scopeOfType(Builder $query, string $type): void
{
$query->where('type', $type);
}
}

После того как ожидаемые аргументы были добавлены в сигнатуру метода вашей области видимости, вы можете передавать аргументы при вызове области видимости:

$users = User::ofType('admin')->get();

Сравнение моделей

Иногда вам может потребоваться определить, являются ли две модели "одинаковыми" или нет. Методы is и isNot можно использовать для быстрой проверки того, имеют ли две модели одинаковый первичный ключ, таблицу и подключение к базе данных или нет:

if ($post->is($anotherPost)) {
// ...
}
 
if ($post->isNot($anotherPost)) {
// ...
}

Методы is и isNot также доступны при использовании отношений belongsTo, hasOne, morphTo и morphOne отношений. Этот метод особенно полезен, когда вы хотите сравнить связанную модель без выполнения запроса для получения этой модели:

if ($post->author()->is($user)) {
// ...
}

События

Примечание Хотите транслировать события Eloquent напрямую в ваше клиентское приложение? Ознакомьтесь с трансляцией событий модели в Laravel.

Модели Eloquent отправляют несколько событий, позволяя вам подключаться к следующим моментам жизненного цикла модели: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored и replicating.

Событие retrieved будет отправлено, когда существующая модель будет извлечена из базы данных. Когда новая модель сохраняется в первый раз, будут отправлены события creating и created. События updating / updated будут отправлены, когда изменяется существующая модель, и вызывается метод save. События saving / saved будут отправлены, когда модель создается или обновляется - даже если атрибуты модели не были изменены. Имена событий, заканчивающиеся на -ing, отправляются перед любыми изменениями в модели, а события, заканчивающиеся на -ed, отправляются после сохранения изменений в модели.

Для начала прослушивания событий модели определите свойство $dispatchesEvents в вашей модели Eloquent. Это свойство сопоставляет различные моменты жизненного цикла модели Eloquent с вашими классами событий. Каждый класс события модели должен ожидать получение экземпляра затронутой модели через свой конструктор:

<?php
 
namespace App\Models;
 
use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
 
class User extends Authenticatable
{
use Notifiable;
 
/**
* Карта событий для модели.
*
* @var array
*/
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}

После определения и сопоставления ваших событий Eloquent вы можете использовать слушатели событий, чтобы обрабатывать события.

Внимание При выполнении массового запроса на обновление или удаление через Eloquent, события модели saved, updated, deleting и deleted не будут отправляться для затронутых моделей. Это происходит потому, что модели фактически никогда не извлекаются при выполнении массовых обновлений или удалений.

Использование замыканий

Вместо использования пользовательских классов событий вы можете зарегистрировать замыкания, которые выполняются, когда возникают различные события модели. Обычно вы должны регистрировать эти замыкания в методе booted вашей модели:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Метод "booted" модели.
*/
protected static function booted(): void
{
static::created(function (User $user) {
// ...
});
}
}

При необходимости вы можете использовать слушателей анонимных событий, поддерживающих работу в очереди при регистрации событий модели. Это указывает Laravel выполнить прослушиватель событий модели в фоновом режиме с использованием очереди вашего приложения:

use function Illuminate\Events\queueable;
 
static::created(queueable(function (User $user) {
// ...
}));

Наблюдатели

Определение наблюдателей

Если вы прослушиваете много событий для определенной модели, вы можете использовать наблюдателей, чтобы сгруппировать все ваши слушатели в один класс. Классы-наблюдатели имеют имена методов, которые отражают события Eloquent, которые вы хотите прослушивать. Команда Artisan make:observer - самый простой способ создать новый класс-наблюдатель:

php artisan make:observer UserObserver --model=User

Эта команда поместит новый наблюдатель в ваш каталог app/Observers. Если этот каталог не существует, Artisan создаст его для вас. Ваш новый наблюдатель будет выглядеть следующим образом:

<?php
 
namespace App\Observers;
 
use App\Models\User;
 
class UserObserver
{
/**
* Обработать событие "создание" пользователя.
*/
public function created(User $user): void
{
// ...
}
 
/**
* Обработать событие "обновление" пользователя.
*/
public function updated(User $user): void
{
// ...
}
 
/**
* Обработать событие "удаление" пользователя.
*/
public function deleted(User $user): void
{
// ...
}
 
/**
* Обработать событие "восстановление" пользователя.
*/
public function restored(User $user): void
{
// ...
}
 
/**
* Обработать событие "полное удаление" пользователя.
*/
public function forceDeleted(User $user): void
{
// ...
}
}

Чтобы зарегистрировать наблюдателя, вам нужно вызвать метод observe на модели, которую вы хотите наблюдать. Вы можете зарегистрировать наблюдателей в методе boot сервис-провайдера App\Providers\EventServiceProvider вашего приложения:

use App\Models\User;
use App\Observers\UserObserver;
 
/**
* Зарегистрировать любые события для вашего приложения.
*/
public function boot(): void
{
User::observe(UserObserver::class);
}

В качестве альтернативы вы можете перечислить свои наблюдатели внутри свойства $observers в классе App\Providers\EventServiceProvider вашего приложения:

use App\Models\User;
use App\Observers\UserObserver;
 
/**
* Наблюдатели модели для вашего приложения.
*
* @var array
*/
protected $observers = [
User::class => [UserObserver::class],
];

Примечание Существуют дополнительные события, которые наблюдатель может прослушивать, такие как saving и retrieved. Эти события описаны в разделе событий документации.

Наблюдатели и транзакции базы данных

Когда модели создаются в пределах транзакции базы данных, иногда вам захочется указать наблюдателю выполнять свои обработчики событий только после фиксации транзакции базы данных. Вы можете сделать это, реализовав интерфейс ShouldHandleEventsAfterCommit в вашем наблюдателе. Если транзакция базы данных не выполняется, обработчики событий будут выполнены немедленно:

<?php
 
namespace App\Observers;
 
use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
 
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* Обработать событие "создание" пользователя.
*/
public function created(User $user): void
{
// ...
}
}

Отключение событий

Иногда вам может потребоваться временно "отключить" все события, генерируемые моделью. Вы можете сделать это с помощью метода withoutEvents. Метод withoutEvents принимает замыкание в качестве единственного аргумента. Любой код, выполненный в пределах этого замыкания, не будет отправлять события модели, и любое значение, возвращенное замыканием, будет возвращено методом withoutEvents:

use App\Models\User;
 
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
 
return User::find(2);
});

Сохранение одной модели без событий

Иногда вам может потребоваться "сохранить" данную модель, не отправляя события. Вы можете сделать это с использованием метода saveQuietly:

$user = User::findOrFail(1);
 
$user->name = 'Victoria Faith';
 
$user->saveQuietly();

Вы также можете "обновлять", "удалять", "мягко удалять", "восстанавливать" и "клонировать" данную модель, не отправляя события:

$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();