Тема
Лайфхаки и приёмы
Малоизвестные возможности Laravel, которые экономят время каждый день. Формат: приём → зачем. Подробности — в официальной доке (
laravel-boost__search-docs).
Artisan — диагностика и интроспекция
| Команда | Что даёт |
|---|---|
php artisan about | Вся диагностика окружения одним экраном: версии, драйверы, кеши. Первое, что смотреть на «у меня не работает» |
php artisan model:show Order | Все поля, касты, связи, observers модели — без открытия кода. Идеально перед доработкой чужой модели |
php artisan db:table orders | Столбцы, индексы, FK таблицы прямо из консоли |
php artisan route:list --except-vendor | Роуты без пакетного шума; --path=api — фильтр по префиксу |
php artisan schedule:list | Все cron-задачи с временем следующего запуска |
php artisan queue:monitor redis:default --max=100 | Алерт при переполнении очереди (в cron) |
php artisan pail | Стрим логов в реальном времени с фильтрами (--filter=, --user=) — вместо tail -f |
php artisan migrate --pretend | Показывает SQL миграции, не выполняя — проверка перед прод-запуском |
php artisan config:show database | Итоговый конфиг после всех env/кешей — когда «почему подключается не туда» |
php artisan tinker | REPL: быстро проверить запрос/связь/скоуп, не создавая тестовый роут |
Eloquent — приёмы, о которых забывают
| Приём | Зачем |
|---|---|
sole() вместо first() | Бросает исключение, если записей не ровно одна — ловит дубли данных на раннем этапе |
createOrFirst() | Безопаснее firstOrCreate() под гонкой: сначала INSERT с ловлей duplicate key (нужен уникальный индекс) |
upsert([...], unique, update) | Массовая вставка-или-обновление одним запросом — основа быстрых импортов |
whereBelongsTo($city) | Читабельнее where('city_id', $city->id) и не ошибётся в имени FK |
is() / isNot() | Сравнение моделей без ->id === ->id |
loadMissing('relation') | Догрузить связь, только если ещё не загружена — без повторного запроса |
withWhereHas('reviews', fn...) | whereHas + eager load тем же условием — одним вызовом, без дублирования замыкания |
chaperone() на hasMany | Обратная ссылка на родителя у детей без N+1 ($review->post не делает запрос) |
value('name') / pluck('name', 'id') | Одно поле/словарь без гидрации моделей — заметно быстрее на больших выборках |
toQuery() | Коллекцию моделей обратно в запрос — для массового update() по выбранному |
withCount() / withSum() / withAvg() | Агрегаты подзапросом вместо загрузки связей ради подсчёта |
lazy() / lazyById() | Обход больших таблиц с константной памятью, интерфейс обычной коллекции |
Валидация — тонкости FormRequest
| Приём | Зачем |
|---|---|
bail первым правилом | Остановить проверку поля на первой ошибке — не гонять дорогие правила (exists) по заведомо пустому |
$request->safe()->only([...]) | Взять из валидированного только нужное — защита от «пролезших» полей при расширении правил |
exclude_if / exclude | Поле проверяется, но не попадает в validated() — для чекбоксов-соглашений |
prohibited_if | Явный запрет поля в определённых условиях — для API, где лишнее поле = ошибка клиента |
Rule::dimensions()->maxWidth(...) | Валидация размеров изображения до обработки |
sometimes() в withValidator | Условные правила, зависящие от других полей — вместо каши в rules() |
stopOnFirstFailure() | Для форм из 20+ полей: не собирать все ошибки, если важна первая |
Blade — директивы, которые сокращают шаблоны
| Директива | Вместо чего |
|---|---|
@class(['active' => $isActive]) | Тернарники в атрибуте class |
@checked($value) / @selected(...) / @disabled(...) | |
@once | Скрипт компонента, который в цикле вставился бы N раз |
@aware(['color']) | Проброс prop родительского компонента вглубь без цепочки атрибутов |
@session('status') | Блок «показать если есть флеш» без @if(session(...)) |
@forelse ... @empty | @if($items->count()) + @foreach (и по стандарту студии — обязателен) |
HTTP-клиент — надёжные интеграции без обвязки
| Приём | Зачем |
|---|---|
Http::retry(3, 200) | Ретраи с паузой из коробки — не писать циклы |
Http::timeout(5)->connectTimeout(2) | Всегда ставить: зависший внешний API не должен вешать ваш запрос |
Http::pool(fn...) | Параллельные запросы к нескольким API — суммарное время = максимум, а не сумма |
->throw() | Превратить 4xx/5xx в исключение — вместо ручной проверки successful() |
Http::fake([...]) в тестах | Мокать внешние API декларативно, Http::assertSent() — проверять вызовы |
Хелперы и утилиты последних версий
| Инструмент | Зачем |
|---|---|
Number::forHumans(12500) → «12,5 тыс.» | Форматирование чисел/размеров/валют с локалью — не писать свои функции |
Str::mask($email, '*', 3) | Замаскировать персональные данные в UI/логах |
Str::squish() | Трим + схлопывание внутренних пробелов — нормализация пользовательского ввода |
Benchmark::dd(fn() => ...) | Быстро замерить кусок кода без Telescope |
Sleep::for(2)->seconds() | Вместо sleep() — в тестах фейкается (Sleep::fake()), не тормозит CI |
once(fn() => ...) | Мемоизация в рамках запроса — для повторных вычислений в цикле |
Context::add('order_id', $id) | Сквозной контекст: попадает во все логи и в очередь вместе с джобом — находка для отладки продовых инцидентов |
defer(fn() => ...) | Выполнить после отправки ответа — лёгкая альтернатива очереди для некритичного (метрики, прогрев) |
rescue(fn() => ..., $default) | try/catch с логированием одной строкой — для некритичных операций |
Carbon и даты
CarbonImmutableпо умолчанию (Date::use(CarbonImmutable::class)в AppServiceProvider): обычный Carbon мутабелен —$date->addDay()меняет исходный объект, это источник трудноуловимых багов в циклах и сравнениях.- Хранить всегда в UTC, конвертировать при выводе.
APP_TIMEZONEменять с UTC — грабли с DST и сравнением дат. - Русские даты —
translatedFormat('d F Y')(по стандарту студии), не ручные массивы месяцев.
Filament — мелочи, экономящие часы
| Приём | Зачем |
|---|---|
->live(onBlur: true) вместо ->live() | Реактивность без запроса на каждый символ — формы заметно отзывчивее |
Tables\Actions\ReplicateAction | «Дублировать запись» бесплатно — частая просьба клиентов |
->defaultGroup('category.name') | Группировка строк таблицы без кастомного кода |
->persistFiltersInSession() | Фильтры переживают уход со страницы — админы это любят |
->recordUrl(null) + explicit actions | Отключить клик по строке, когда он открывает не то, что ждут |
Section::make()->aside() | Форма «поля слева, описание справа» — читабельнее для длинных форм |
->helperText() / ->hint() | Подсказки прямо в форме — меньше вопросов от контент-менеджера |
php artisan make:filament-issue | Воспроизводимый багрепорт Filament с версиями окружения |
Работа с очередями — практика
->afterCommit()на dispatch внутри транзакции: иначе воркер может взять джоб раньше, чем транзакция закоммитится, и не найти запись (классический «job model not found»).Bus::batch()для пакетов: прогресс,then/catch/finally, отмена — не городить свой учёт.WithoutOverlappingmiddleware: джобы по одному ресурсу не выполняются параллельно.ShouldBeUnique: защита от дублей джобов в очереди (например, «пересчитать рейтинг»).- Разные очереди по приоритету (
notifications,heavy) — по стандарту студии; воркер слушает--queue=high,defaultв порядке приоритета.