Рассмотрим новый функционал, появившийся в Laravel с момента первоначального релиза версии 8. Сегодня поговорим о новых методах в Database и Eloquent. Пройдемся прямо по каждой версии, в которых появлялось что-то новое.
8.5 Метод crossJoinSub
конструктора запросов
Используем подзапрос CROSS JOIN
use Illuminate\Support\Facades\DB; $totalQuery = DB::table('orders')->selectRaw('SUM(price) as total'); DB::table('orders') ->select('*') ->crossJoinSub($totalQuery, 'overall') ->selectRaw('(price / overall.total) * 100 AS percent_of_total') ->get();
8.10 Метод is()
отношений Один-к-одному для сравнения моделей
Теперь мы можем произвести сравнение между связанными моделями без дополнительного обращения к базе данных.
// ДО: внешний ключ берется из модели Post $post->author_id === $user->id; // ДО: выполняется дополнительный запрос для получения модели User из отношений Author $post->author->is($user); // ПОСЛЕ $post->author()->is($user);
8.10 Метод upsert()
Если нужно выполнить несколько upserts
в одном запросе, то можно использовать метод upsert
вместо вызова нескольких updateOrCreate
.
Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], ['departure', 'destination'], ['price']);
8.12 Метод explain()
Позволяет получить информацию о запросе из SQL.
User::where('name', 'Illia Sakovich')->explain(); User::where('name', 'Illia Sakovich')->explain()->dd();
8.15 Поддержка ограничений жадной загрузки отношений MorphTo
Eloquent, при жадной загрузке отношений morphTo
, выполняет несколько запросов для получения каждого типа связанной модели. Теперь вы можете добавить дополнительные ограничения к каждому такому запросу, используя метод constrain
.
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\MorphTo; $comments = Comment::with(['commentable' => function (MorphTo $morphTo) { $morphTo->constrain([ Post::class => function (Builder $query) { $query->whereNull('hidden_at'); }, Video::class => function (Builder $query) { $query->where('type', 'educational'); }, ]); }])->get();
8.17.2 Метод BelongsToMany::orderByPivot()
Позволяет вам напрямую сортировать результаты запроса отношений BelongsToMany
.
class Tag extends Model { public $table = 'tags'; } class Post extends Model { public $table = 'posts'; public function tags() { return $this->belongsToMany(Tag::class, 'posts_tags', 'post_id', 'tag_id') ->using(PostTagPivot::class) ->withTimestamps() ->withPivot('flag'); } } class PostTagPivot extends Pivot { protected $table = 'posts_tags'; } // Где-то в Контроллере public function getPostTags($id) { return Post::findOrFail($id)->tags()->orderPivotBy('flag', 'desc')->get(); }
8.23 Метод BuildsQueries::sole()
Метод sole()
вернёт только одну запись, соответствующую критериям. Если такая запись не найдена, то будет выброшено исключение NoRecordsFoundException
. Если будет найдено несколько записей, то выбросится исключение MultipleRecordsFoundException
.
DB::table('products')->where('ref', '#123')->sole()
8.27 Возможность добавлять несколько полей после определенного поля
Метод after()
теперь может использоваться для добавление нескольких полей.
Schema::table('users', function (Blueprint $table) { $table->after('remember_token', function ($table){ $table->string('card_brand')->nullable(); $table->string('card_last_four', 4)->nullable(); }); });
8.37 Анонимные миграции
Laravel автоматически присваивает имя класса всем миграциям. Теперь вы можете вернуть анонимный класс из файла миграции.
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::table('people', function (Blueprint $table) { $table->string('first_name')->nullable(); }); } };
8.27 Метод chunkMap()
Похож на метод each()
, но проще в использовании. Автоматически разбивает результат на части (chanks — чанки).
return User::orderBy('name')->chunkMap(fn ($user) => [ 'id' => $user->id, 'name' => $user->name, ]), 25);
8.28 Кастомные касты ArrayObject
+ Collection
Приведение json к PHP-экземпляру ArrayObject
, который позволяет объекту вести себя как массив.
// Внутри модели... $casts = ['options' => AsArrayObject::class]; // Изменяем параметры $user = User::find(1); $user->options['foo']['bar'] = 'baz'; $user->save();
8.40 Eloquent\Builder::withOnly()
Метод для переопределения значений для конкретного запроса, заданных в параметре $with
.
class Product extends Model{ protected $with = ['prices', 'colours', 'brand']; public function colours(){ ... } public function prices(){ ... } public function brand(){ ... } } Product::withOnly(['brand'])->get();
8.41 Cursor-пагинация
Подробнее об этом виде новой пагинации — «Пагинация: Offset против Cursor». Хорошо подходит для больших наборов данных и интерфейсов с «бесконечной» прокруткой.
use App\Models\User; use Illuminate\Support\Facades\DB; $users = User::orderBy('id')->cursorPaginate(10); $users = DB::table('users')->orderBy('id')->cursorPaginate(10);
8.12 Методы withMax
, withMin
, withSum
и withAvg
для QueriesRelationships
В дополнении к методу withCount
, Eloquent теперь поддерживает методы withMin
, withMax
, withAvg
и withSum
.
Они добавляют атрибут {relation}_{function}_{column}
в результат запроса.
Post::withCount('comments'); Post::withMin('comments', 'created_at'); Post::withMax('comments', 'created_at'); Post::withSum('comments', 'foo'); Post::withAvg('comments', 'foo');
Под капотом используется метод withAggregate
.
Post::withAggregate('comments', 'created_at', 'distinct'); Post::withAggregate('comments', 'content', 'length'); Post::withAggregate('comments', 'created_at', 'custom_function'); Comment::withAggregate('post', 'title'); Post::withAggregate('comments', 'content');
8.13 Добавлены методы loadMax
, loadMin
, loadSum
и loadAvg
в Eloquent\Collection и методы loadMax
, loadMin
, loadSum
, loadAvg
, loadMorphMax
, loadMorphMin
, loadMorphSum
и loadMorphAvg
в Eloquent\Model
// Eloquent/Collection public function loadAggregate($relations, $column, $function = null) {...} public function loadCount($relations) {...} public function loadMax($relations, $column) {...} public function loadMin($relations, $column) {...} public function loadSum($relations, $column) {...} public function loadAvg($relations, $column) {...} // Eloquent/Model public function loadAggregate($relations, $column, $function = null) {...} public function loadCount($relations) {...} public function loadMax($relations, $column) {...} public function loadMin($relations, $column) {...} public function loadSum($relations, $column) {...} public function loadAvg($relations, $column) {...} public function loadMorphAggregate($relation, $relations, $column, $function = null) {...} public function loadMorphCount($relation, $relations) {...} public function loadMorphMax($relation, $relations, $column) {...} public function loadMorphMin($relation, $relations, $column) {...} public function loadMorphSum($relation, $relations, $column) {...} public function loadMorphAvg($relation, $relations, $column) {...}
8.13 Модифицирован метод QueriesRelationships::has()
для поддержки отношений MorphTo
Добавлено условие count/exists в запрос полиморфических отношений
public function hasMorph($relation, ...) public function orHasMorph($relation,...) public function doesntHaveMorph($relation, ...) public function whereHasMorph($relation, ...) public function orWhereHasMorph($relation, ...) public function orHasMorph($relation, ...) public function doesntHaveMorph($relation, ...) public function orDoesntHaveMorph($relation,...)
Пример с замыканием для кастомизации запроса отношений:
// Получить комментарии, связанные с сообщениями или видео, при условии, что заголовок содержит «code%» $comments = Comment::whereHasMorph( 'commentable', [Post::class, Video::class], function (Builder $query) { $query->where('title', 'like', 'code%'); } )->get(); // Получить комментарии, связанные с сообщениями, при условии, что заголовок НЕ содержит «code%» $comments = Comment::whereDoesntHaveMorph( 'commentable', Post::class, function (Builder $query) { $query->where('title', 'like', 'code%'); } )->get();
8.41 Метод Model::updateQuietly()
Иногда нужно обновить модель без отправки каких-либо событий. Теперь можно сделать это с помощью метода updateQuietly()
, который под капотом использует метод saveQuietly()
.
$flight->updateQuietly(['departed' => false]);
С версии 8.59, вы также можете использовать методы createOneQuietly
, createManyQuietly
и createQuietly
при использовании Фабрики Моделей.
Post::factory()->createOneQuietly(); Post::factory()->count(3)->createQuietly(); Post::factory()->createManyQuietly([ ['message' => 'A new comment'], ['message' => 'Another new comment'], ]);
8.41 Извлечение ключа модели в id для whereKey()
and whereKeyNot()
Можно использовать экземпляр модели с методами whereKey()
и whereKeyNot()
.
$passenger->tickets() ->whereHas('airline', fn (Builder $query) => $query->whereKey($airline)) ->get();
8.42 Метод withExists
в QueriesRelationships
В дополнении к методу withCount
, вы можете использовать метод withExists
для проверки наличия отношений.
// ДО: $users = User::withCount('posts')->get(); $isAuthor = $user->posts_count > 0; // ПОСЛЕ: $users = User::withExists('posts')->get(); $isAuthor = $user->posts_exists; // поле name может быть также псевдонимом the column name can also be aliased: $users = User::withExists([ 'posts as is_author', 'posts as is_tech_author' => function ($query) { return $query->where('category', 'tech'); }, 'comments', ])->get();
8.42 Метод loadExists
Продолжая работу с вышеупомянутым методом withExists
— теперь вы можете использовать loadExists
в Моделях и Коллекциях.
$books = Book::all(); $books->loadExists(['author', 'publisher']);
8.42 Добавлено отношение «Один из многих»
Подробнее об этом отношении вы можете прочитать в статье Отношения «One of Many».
Это отношение создает связь Один-к-одному из отношений Один-ко-многим. Например, «последний вход в систему», «первый вход», цена на продукт (то есть получить актуальную цену товара).
/** * Получить самый последний заказ пользователя */ public function latestOrder() { return $this->hasOne(Order::class)->latestOfMany(); } /** * Получить самый старый заказ пользователя */ public function oldestOrder() { return $this->hasOne(Order::class)->oldestOfMany(); } /** * Получить самый дорогой заказ пользователя */ public function largestOrder() { return $this->hasOne(Order::class)->ofMany('price', 'max'); }
8.43 Строгий режим загрузки
Добавляет возможность включить строгий режим для предотвращения ленивой загрузки отношений. Для этого нужно вызвать метод Model::preventLazyLoading()
в методе boot()
вашего приложения.
Model::preventLazyLoading(! app()->isProduction());
8.43 Метод beforeQuery
Позволяет изменить конструктор «subselect», при этом изменения, также будут применяться к родительскому конструктору запросов. Сохраненные замыкания будут вызываться перед обработкой запроса. Таким образом, вы можете сохранить конструтор подзапросов и изменять подзапросы после применения к родительскому конструктору:
// 1. Добавляем подзапрос $builder->beforeQuery(function ($query) use ($subQuery) { $query->joinSub($subQuery, ...); }); // 2. Добавляем условия подзапроса $subQuery->where('foo', 'bar'); // 3. Выполняем подзапрос с применением условий из пункта 2 $builder->get();
8.50 Трейт Prunable
для моделей
Для переодической очистки моделей от устаревших записей. С помощью этого трейта Laravel будет делать это автоматически, только нужно будет настроить частоту выполнение команды model:prune
в классе Kernel
. Подробнее в статье «Очистка моделей».
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Prunable; class Flight extends Model { use Prunable; /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { return static::where('created_at', '<=', now()->subMonth()); } }
Также, в методе pruning
можно задать действия, которые должны быть выполнены перед удалением модели:
protected function pruning() { // Удаление дополнительных ресурсов, // связанных с моделью. Например, файлы. Storage::disk('s3')->delete($this->filename); }
8.53 Иммутабельные даты и приведение к ним
Более подробно о нововведении — «Иммутабельные даты в Laravel».
Приведение к CarbonImmutable
вместо обычного экземпляра Carbon
.
class User extends Model { public $casts = [ 'date_field' => 'immutable_date', 'datetime_field' => 'immutable_datetime', ]; }
8.57 Хелпер where
для отношений
Синтаксический сахар whereRelation
и whereMorphRelation
для запроса отношений через whereHas
с простым условием where
.
// ДО User::whereHas('posts', function ($query) { $query->where('published_at', '>', now()); })->get(); // ПОСЛЕ User::whereRelation('posts', 'published_at', '>', now())->get(); // Полиморфные отношения Comment::whereMorphRelation('commentable', '*', 'public', true);
8.59 Метод whereMorphedTo
Новые методы whereMorphedTo
и orWhereMorphedTo
для упрощения поиска морфированных моделей без затрат на подзапрос whereHas
.
Feedback::whereMorphedTo('subject', $user)->get(); Feedback::whereMorphedTo('subject', User::class)->get();
8.59 Возможность запретить морфирование
Вы можете вызвать метод enforceMorphMap
в методе boot
класса AppServiceProvider
для запрета морфирования без карты.
use Illuminate\Database\Eloquent\Relations\Relation; Relation::enforceMorphMap([ 'post' => Post::class, 'video' => Video::class, ]);
8.60 Метод valueOfFail()
В дополнении к методу value()
появился новый метод valueOrFail
. Он выбросит исключение ModelNotFoundException
, если модель не будет найдена.
// ДО: $votes = User::where('name', 'John')->firstOrFail('votes')->votes; // ПОСЛЕ: $votes = User::where('name', 'John')->valueOrFail('votes');
8.63 Метод whereBelongsTo()
Новый метод автоматически определяет правильные отношения и внешний ключ для указанной модели.
// ДО: $posts = Post::where('user_id', $user->id)->get(); // ПОСЛЕ: $posts = Post::whereBelongsTo($user)->get();
Автор: Pascal Baljet
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.