Скрытые сокровища Laravel: 18 советов для профессиональных разработчиков

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

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

Вне зависимости от того, масштабируете ли вы серьёзный SaaS или экспериментируете с чем-то новым, нет никакого оправдания трате времени на решение проблем, уже решённых в Laravel. Эти советы помогут извлечь максимум пользы из фреймворка: чистый код, меньше хаков и больше времени, потраченного на реальное создание.

Итак, давайте разбираться.

Используйте withRelationshipAutoloading() для решения проблемы N+1

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

Чтобы избежать этого, используйте жадную загрузку с помощью методов типа with() для загрузки связанных данных в одном запросе, что повышает производительность и снижает ненужные накладные расходы базы данных.

// С жадной загрузкой
$orders = Order::with('client.owner.company')->get();

foreach ($orders as $order) {
echo $order->client->owner->company->name; // Только два запроса: один для заказов и один для отношений
}

В Laravel 12.8 в моделях и коллекциях Eloquent появился новый метод withRelationshipAutoloading(). При его вызове происходит автоматическая загрузка связей при каждом обращении к ним, без необходимости явного вызова load() или with().

$orders = Order::all()->withRelationshipAutoloading();

foreach ($orders as $order) {
echo $order->client->owner->company->name;
}

Кроме того, вы можете активировать жадную загрузку для всех моделей с помощью следующего вызова (в AppServiceProvider)

public function boot() {
Model::AutomaticallyEagerLoadRelationships();
}

Довольно удобно, не так ли?

Используйте lazy() вместо get() для больших массивов данных

При обработке большого количества строк lazy() может значительно сократить расход памяти:

User::lazy()->each(function ($user) {
// Сделать что-нибудь с каждым пользователем
});

Загружает записи по одной, используя под капотом курсор.

Используйте defer() для очистки после ответа

Начиная с версии 10, Laravel позволяет отложить логику до момента отправки ответа:

defer(function () {
// Очистка временных файлов
Storage::delete($this->tempFile);
});

Идеально подходит для несрочной фоновой работы, не требующей очереди, например удаления файла, отправки электронной почты или сохранения аналитики запросов. С примера использования можно ознакомиться в статье Пять примеров использования defer() в Laravel

Используйте привязки модели к маршруту в Laravel

Привязка модели к маршруту — мощная функция в Laravel, позволяющая автоматически внедрять модели в обратные вызовы маршрута. Вместо ручного получения модели по ID или другому идентификатору, Laravel автоматически запрашивает модель и внедряет её в экшен маршрута.

По умолчанию Laravel разрешает модель по её первичному ключу (обычно id). Однако это поведение можно настроить, разрешая модели по различным полям, например, по уникальному slug, username или email.

Route::get('/users/{user}', fn(User $user) => $user);

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

Route::get('blog/{slug}', [BlogController::class, 'show']);

Обычно вы получаете посты вручную в контроллере:

use App\Models\Post;

class BlogController extends Controller
{
public function show($slug)
{
$post = Post::where('slug', $slug)->firstOrFail();

// .... сделать что-нибудь с постом
}
}

Упростить эту задачу можно, обновив маршрут, чтобы привязка осуществлялась непосредственно по slug:

Route::get('blog/{post:slug}', [BlogController::class, 'show']);

Теперь Laravel автоматически разрешает модель Post с помощью slug и внедряет её в метод контроллера:

class BlogController extends Controller
{
public function show(Post $post)
{
// ... сделать что-нибудь с постом
}
}

Пользовательские касты для чистых моделей

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

Пример:

class Uppercase implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return strtoupper($value);
}

public function set($model, string $key, $value, array $attributes)
{
return strtolower($value);
}
}

Применение:

protected $casts = [
'username' => Uppercase::class,
];

Узнать больше о кастах моделей можно в статье Создание кастов моделей в Laravel

Псевдонимы Tinker

Tinker поддерживает глобальные псевдонимы! Создайте файл .tinker.php в корне проекта:

use App\Models\User;

function admin() {
return User::where('email', 'admin@example.com')->first();
}

Теперь можно вызывать admin() прямо в Tinker.

Отладка медленных запросов с DB::listen()

Удобный мониторинг и регистрация всех SQL-запросов в приложении для выявления узких мест в производительности.

Пример:

DB::listen(function ($query) {
logger($query->sql, $query->bindings, $query->time);
});

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

Ознакомится с дополнительными материалами по отладке SQL запросов можно в нашей статье Отладка SQL запросов в Laravel

Повторное использование логики валидации с Rule::when()

В Laravel можно упростить логику условной валидации с Rule::when(). Это позволяет динамически применять правила на основе определённых условий, делая логику валидации более чистой и читабельной. В отличие от sometimes(), способного запутать сложные условия, Rule::when() предлагает более простой подход к условному применению правил валидации.

Используйте условную логику внутри определений правил:

use Illuminate\Validation\Rule;

Rule::when($user->isAdmin(), ['required', 'email']);

Используйте правило password для проверки надёжности пароля

В Laravel есть встроенное правило password, позволяющее применять строгие политики паролей:

'password' => ['required', 'string', Password::min(8)->letters()->symbols()],

Больше не нужно вручную писать regex.

Больше о редко используемых правилах валидации можно узнать в нашей статье Десять редко используемых правил валидации Laravel

Используйте artisan model:prune для автоматической очистки старых записей

Хотите удалить старые логи или временные записи?

Добавьте в модель:

use Illuminate\Database\Eloquent\Prunable;

public function prunable()
{
return $this->where('created_at', '<', now()->subMonth());
}

Затем добавьте это в расписание:

$schedule->command('model:prune')->daily();

Это позволит удалить записи, старше 1 месяца.

Определяйте defaults() в маршрутах

Предварительное заполнение параметров маршрута, чтобы URL-адреса оставались чистыми:

Route::get('reports/{year}/{month}', ReportController::class)
->defaults('year', now()->year)
->defaults('month', now()->month);

Теперь в /reports будут автоматически проставляться текущий год и месяц, если они были пропущены.

Используйте Model::withoutEvents(), когда нужна тишина

Иногда необходимо обновить или создать модель без запуска наблюдателей/событий:

User::withoutEvents(function () {
User::factory()->count(10)->create();
});

Очень удобно для засева или тихих экшенов администратора.

fresh() и refresh() в Eloquent — это не одно и то же

$user->name = 'John';
$user->refresh(); // Отменяет изменения и перезагружает

$newUser = $user->fresh(); // В $user остались изменения, а в $newUser - нет

Небольшая разница, огромное воздействие.

Используйте middleware password.confirm для конфиденциальных маршрутов

Хотите защитить страницы профиля или биллинга?

Route::middleware(['auth', 'password.confirm'])->group(function () {
Route::get('/billing', BillingController::class);
});

Laravel попросит пользователя повторно ввести пароль, прежде чем продолжить работу.

Узнать об управлении маршрутами можно из нашей статьи: Laravel: Управление маршрутами в большом приложении

Ставьте в очередь замыкания с dispatch(function () {})

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

dispatch(function () use ($user) {
Mail::to($user)->send(new WelcomeMail($user));
});

Очень удобно для небольших разовых фоновых задач.

Используйте Route::fallback() для перехвата всех несопоставленных маршрутов

Грациозно обрабатывайте 404 или перенаправляйте нежелательные запросы:

Route::fallback(function () {
return response()->view('errors.404', [], 404);
});

Или записывайте их в лог, перенаправляйте на дружественные страницы или отсылайте уведомление.

Используйте tap() для устранения побочных эффектов Fluent

Используйте tap(), когда необходимо выполнить определённое действие во время цепочки, не разрывая её:

$user = tap(User::find(1), function ($user) {
Log::info("Fetched user: {$user->email}");
});

Сохраняет читаемость и функциональность.

Используйте ->diffForHumans() для дат

Laravel делает даты удобочитаемыми без особых усилий:

$user->created_at->diffForHumans(); // например, "3 часа назад".

Используйте хелпер retry() для отказоустойчивых операций

Хелпер retry() идеально подходит для работы с нестабильными API, блокировками баз данных или ненадёжными сервисами. Он делает работу кода более надёжной, автоматически повторяя неудачные операции.

Пример:

return retry(3, function () {
return Http::get('https://external-api.com/data');
}, 100); // повторяет попытку 3 раза, с задержкой 100 мс

Хелпер retry() добавляет встроенную отказоустойчивость в приложение, обеспечивая более гладкое взаимодействие с ненадёжными внешними сервисами.

Заключение

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

Продолжайте создавать отличное программное обеспечение!

Комментарии


Дополнительные материалы

Предыдущая Статья

Итак, вы хотите отказаться от пре- и постпроцессоров CSS...