- Введение
- Генерация классов моделей
- Конвенции моделей Eloquent
- Получение моделей
- Получение одиночных моделей / агрегатов
- Вставка и обновление моделей
- Удаление моделей
- Обрезка моделей
- Копирование моделей
- Области запросов
- Сравнение моделей
- События
#Введение
Laravel включает Eloquent — объектно-реляционный маппер (ORM), который делает работу с базой данных удобной и приятной. При использовании Eloquent каждая таблица базы данных имеет соответствующую «модель», которая используется для взаимодействия с этой таблицей. Помимо получения записей из таблицы, модели Eloquent позволяют вставлять, обновлять и удалять записи.
Перед началом работы обязательно настройте подключение к базе данных в конфигурационном файле вашего приложения config/database.php. Для получения дополнительной информации о настройке базы данных ознакомьтесь с документацией по конфигурации базы данных.
#Laravel Bootcamp
Если вы новичок в Laravel, рекомендуем пройти Laravel Bootcamp. Laravel Bootcamp проведёт вас через процесс создания вашего первого приложения на Laravel с использованием Eloquent. Это отличный способ познакомиться со всеми возможностями Laravel и Eloquent.
#Генерация классов моделей
Чтобы начать, давайте создадим модель Eloquent. Модели обычно находятся в директории app\Models и наследуются от класса Illuminate\Database\Eloquent\Model. Для генерации новой модели можно воспользоваться командой make:model — командой Artisan:
php artisan make:model Flight
Если вы хотите сгенерировать миграцию базы данных вместе с моделью, используйте опцию --migration или -m:
php artisan make:model Flight --migration
При генерации модели можно создавать и другие типы классов, такие как фабрики, сидеры, политики, контроллеры и form requests. Кроме того, эти опции можно комбинировать для создания нескольких классов одновременно:
# Сгенерировать модель и класс FlightFactory...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# Сгенерировать модель и класс FlightSeeder...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# Сгенерировать модель и класс FlightController...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# Сгенерировать модель, ресурсный класс FlightController и классы form request...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# Сгенерировать модель и класс FlightPolicy...
php artisan make:model Flight --policy
# Сгенерировать модель, миграцию, фабрику, сидер и контроллер...
php artisan make:model Flight -mfsc
# Быстрый способ сгенерировать модель, миграцию, фабрику, сидер, политику, контроллер и form requests...
php artisan make:model Flight --all
# Сгенерировать pivot-модель...
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. По конвенции используется имя таблицы во множественном числе в snake_case, если не указано иное. Таким образом, 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 предполагает, что первичный ключ — это автоинкрементное целочисленное значение, поэтому автоматически приводит его к типу integer. Если вы хотите использовать первичный ключ без автоинкремента или нечисловой, необходимо определить публичное свойство $incrementing в модели и установить его в false:
<?php
class Flight extends Model
{
/**
* Указывает, является ли ID модели автоинкрементным.
*
* @var bool
*/
public $incrementing = false;
}
Если первичный ключ модели не является целым числом, следует определить защищённое свойство $keyType в модели. Это свойство должно иметь значение string:
<?php
class Flight extends Model
{
/**
* Тип данных первичного ключа.
*
* @var string
*/
protected $keyType = 'string';
}
#«Составные» первичные ключи
Eloquent требует, чтобы у каждой модели был хотя бы один уникально идентифицирующий «ID», который служит первичным ключом. «Составные» первичные ключи не поддерживаются моделями Eloquent. Однако вы можете добавлять дополнительные уникальные индексы с несколькими столбцами в таблицы базы данных помимо уникального первичного ключа.
#UUID и ULID ключи
Вместо использования автоинкрементных целочисленных первичных ключей вы можете выбрать 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'];
}
Если хотите, вы можете использовать «ULID» вместо UUID. 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 автоматически устанавливает значения этих столбцов при создании или обновлении моделей. Если вы не хотите, чтобы эти столбцы управлялись автоматически, определите свойство $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 принимает необязательный булевый аргумент, указывающий, следует ли запрещать ленивую загрузку. Например, вы можете отключить ленивую загрузку только в нерабочих (не production) средах, чтобы в продакшене всё работало нормально, даже если случайно присутствует лениво загруженная связь. Обычно этот метод вызывается в методе boot вашего AppServiceProvider:
use Illuminate\Database\Eloquent\Model;
/**
* Инициализация сервисов приложения.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
Также вы можете заставить Laravel выбрасывать исключение при попытке заполнить незаполняемый атрибут, вызвав метод preventSilentlyDiscardingAttributes. Это поможет избежать неожиданных ошибок во время локальной разработки при попытке установить атрибут, не добавленный в массив fillable модели:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
#Получение моделей
После создания модели и соответствующей таблицы базы данных вы готовы начать получать данные из базы. Каждая модель Eloquent — это мощный query builder, позволяющий удобно строить запросы к связанной таблице. Метод all модели вернёт все записи из таблицы:
use App\Models\Flight;
foreach (Flight::all() as $flight) {
echo $flight->name;
}
#Построение запросов
Метод all возвращает все записи из таблицы модели. Однако, поскольку каждая модель Eloquent является query builder, вы можете добавлять дополнительные ограничения к запросам и затем вызывать метод get для получения результатов:
$flights = Flight::where('active', 1)
->orderBy('name')
->take(10)
->get();
Поскольку модели Eloquent являются query builder, ознакомьтесь со всеми методами, предоставляемыми query builder 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 Collection расширяет базовый класс 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 iterable, вы можете перебирать коллекции так же, как массивы:
foreach ($flights as $flight) {
echo $flight->name;
}
#Обработка результатов по частям
Ваше приложение может исчерпать память, если попытаться загрузить десятки тысяч записей Eloquent с помощью методов all или get. Вместо этого используйте метод chunk для более эффективной обработки большого количества моделей.
Метод chunk получает подмножество моделей 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 не создаются до тех пор, пока вы не начнёте их перебирать. Таким образом, в памяти одновременно находится только одна модель при переборе курсора.
Поскольку метод 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 query builder, мы можем выбрать все 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 query builder поддерживает подзапросы. Продолжая пример с рейсами, мы можем отсортировать все пункты назначения по времени прибытия последнего рейса. Это также выполняется одним запросом к базе:
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'
]);
// Получить рейс по имени или создать с атрибутами name, delayed и arrival_time...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// Получить рейс по имени или создать новый экземпляр Flight...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);
// Получить рейс по имени или создать с атрибутами name, delayed и arrival_time...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);
#Получение агрегатов
При работе с моделями Eloquent вы также можете использовать методы агрегирования, такие как count, sum, max и другие агрегатные методы, предоставляемые Laravel query builder. Как и ожидалось, эти методы возвращают скалярное значение, а не экземпляр модели 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 по умолчанию защищены от уязвимостей массового присвоения. Подробнее о массовом присвоении смотрите в разделе mass assignment documentation.
#Обновления
Метод save также можно использовать для обновления моделей, уже существующих в базе данных. Чтобы обновить модель, её нужно получить, задать необходимые атрибуты и вызвать метод save. Метка времени updated_at обновится автоматически, поэтому её не нужно задавать вручную:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();
#Массовые обновления
Обновления можно выполнять и для моделей, соответствующих заданному запросу. В этом примере все рейсы, у которых active равно 1 и destination — San Diego, будут помечены как задержанные:
Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
Метод update ожидает array пар вида «столбец => значение», указывающих, какие столбцы нужно обновить. Метод 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; // john@example.com
$user->name = "Jack";
$user->name; // Jack
$user->getOriginal('name'); // John
$user->getOriginal(); // Массив исходных атрибутов...
#Массовое присвоение
Вы можете использовать метод 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]
);
Если вы хотите выполнить несколько «upsert»-операций в одном запросе, используйте метод 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 имели индекс "primary" или "unique". Кроме того, драйвер MySQL игнорирует второй аргумент метода upsert и всегда использует "primary" и "unique" индексы таблицы для определения существующих записей.
#Удаление моделей
Чтобы удалить модель, вызовите метод delete у экземпляра модели:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->delete();
Можно вызвать метод truncate, чтобы удалить все записи, связанные с моделью. Операция truncate также сбросит автоинкрементные ID в таблице модели:
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 schema builder есть вспомогательный метод для создания этого столбца:
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();
#Очистка моделей (Pruning)
Иногда нужно периодически удалять модели, которые больше не нужны. Для этого можно добавить трейд Illuminate\Database\Eloquent\Prunable или Illuminate\Database\Eloquent\MassPrunable в нужные модели. После добавления трейда реализуйте метод prunable, который возвращает Eloquent query builder с моделями, подлежащими удалению:
<?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'
]);
#Области запросов (Query Scopes)
#Глобальные области
Глобальные области позволяют добавлять ограничения ко всем запросам для данной модели. Встроенная функциональность 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-запроса.
#Применение глобальных скоупов
Чтобы назначить глобальный скоуп модели, достаточно добавить атрибут ScopedBy к модели:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([AncientScope::class])]
class User extends Model
{
//
}
Или вы можете вручную зарегистрировать глобальный скоуп, переопределив метод 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();
#Локальные скоупы
Локальные скоупы позволяют определить общие наборы ограничений для запросов, которые можно легко переиспользовать в приложении. Например, часто может понадобиться получить всех пользователей, считающихся «популярными». Чтобы определить скоуп, добавьте префикс scope к методу модели Eloquent.
Скоупы всегда должны возвращать тот же экземпляр построителя запросов или 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 relationships. Это особенно полезно, когда нужно сравнить связанную модель без выполнения запроса для её получения:
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. Это свойство сопоставляет различные этапы жизненного цикла модели с вашими собственными классами событий. Каждый класс события модели должен принимать экземпляр затронутой модели через конструктор:
<?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
{
/**
* Обработать событие "created" модели User.
*/
public function created(User $user): void
{
// ...
}
/**
* Обработать событие "updated" модели User.
*/
public function updated(User $user): void
{
// ...
}
/**
* Обработать событие "deleted" модели User.
*/
public function deleted(User $user): void
{
// ...
}
/**
* Обработать событие "restored" модели User.
*/
public function restored(User $user): void
{
// ...
}
/**
* Обработать событие "forceDeleted" модели User.
*/
public function forceDeleted(User $user): void
{
// ...
}
}
Чтобы зарегистрировать наблюдателя, добавьте атрибут ObservedBy к соответствующей модели:
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
//
}
Или вы можете вручную зарегистрировать наблюдателя, вызвав метод observe у модели, которую хотите наблюдать. Регистрацию наблюдателей обычно делают в методе boot сервис-провайдера App\Providers\EventServiceProvider вашего приложения:
use App\Models\User;
use App\Observers\UserObserver;
/**
* Зарегистрировать события для приложения.
*/
public function boot(): void
{
User::observe(UserObserver::class);
}
Наблюдатель может слушать и другие события, например saving и retrieved. Эти события описаны в разделе события.
#Наблюдатели и транзакции базы данных
Если модели создаются внутри транзакции базы данных, можно настроить наблюдателя так, чтобы его обработчики событий выполнялись только после фиксации транзакции. Для этого реализуйте интерфейс ShouldHandleEventsAfterCommit в вашем наблюдателе. Если транзакция не активна, обработчики выполняются сразу:
<?php
namespace App\Observers;
use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* Обработать событие "created" модели User.
*/
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();