Скрытые сокровища Laravel: 18 советов для профессиональных разработчиков
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 — это не одно и то же
refresh()
перезагружает модель на месте из базы данных.fresh()
возвращает новый экземпляр.
$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 многое предлагает из коробки, понимание и использование этих продвинутых возможностей может выделить вас как разработчика, не просто использующего фреймворк, а знающего, как заставить его работать в полную силу. Не бойтесь углубляться в эти инструменты, они призваны помочь создавать более качественные, быстрые и удобные в обслуживании приложения.
Продолжайте создавать отличное программное обеспечение!