Рассмотрим новый функционал, появившийся в 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.
