Тема
Laravel 13 — новинки и лучшие практики
Источники: laravel.com/docs/13.x (releases, upgrade), официальный блог Laravel, Laravel News, Spatie, LaravelDaily, community deep-dives (сверено 2026-07). Обязательные правила L13: specs/laravel_13/ (выбор версии — specs/README). Здесь — контекст «почему» и практики, которых нет в спеках. AI SDK и семантический поиск — отдельно: ai-sdk-vector-search.md.
Философия релиза
L13 (17.03.2026, PHP 8.3–8.5) — «модернизация без разрушения»: минимум breaking changes, официальная оценка апгрейда — ~10 минут, все новые фичи опциональны. Bug fixes до Q3 2027, security fixes до 17.03.2028. Сообщество называет его «едва ли не самым лёгким мажорным апгрейдом в истории фреймворка» — но см. «тихие» риски ниже.
Три кита релиза: PHP-атрибуты по всему фреймворку, AI-инфраструктура (first-party AI SDK + vector search) и усиленная CSRF-модель.
PHP-атрибуты — декларативная конфигурация
L13 добавил 36+ атрибутов (всего 50+). Конфигурация живёт рядом с кодом, а не разбросана по провайдерам и методам. Старый синтаксис работает параллельно — это не миграция.
Контроллеры — #[Middleware] и #[Authorize]:
php
use Illuminate\Routing\Attributes\Controllers\{Middleware, Authorize};
#[Middleware('auth')]
#[Middleware('subscribed', except: ['index'])]
class CommentController
{
#[Authorize('create', [Comment::class, 'post'])] // ability + модель/route-параметр
public function store(Post $post) { /* ... */ }
#[Authorize('delete', 'comment')]
public function destroy(Comment $comment) { /* ... */ }
}Middleware метода сливается с middleware класса, не заменяет его. #[Authorize] — сокращение для can-middleware, policy остаётся единственным источником правил доступа.
Jobs — #[Tries], #[Backoff], #[Timeout], #[FailOnTimeout], #[MaxExceptions], #[Queue]:
php
#[Tries(25)]
#[MaxExceptions(3)] // много release-попыток допустимо, но 3 необработанных исключения = failed
#[Timeout(120)] // < retry_after; требует PCNTL
#[FailOnTimeout] // таймаут → сразу failed, не возврат в очередь
class ProcessPodcast implements ShouldQueue { /* ... */ }Eloquent — releases.md заявляет расширение атрибутов на модели, события, нотификации, валидацию и тесты; полный разбор 36 атрибутов (#[Table], #[Hidden], #[Fillable] и др.) — у LaravelDaily (см. источники).
Практика: выбрать один стиль на проект. Смешивать #[Tries(5)] в одной джобе и public $tries = 5; в соседней — хаос для ревью и для AI-агентов. Для новых L13-проектов консенсус — атрибуты: декларативно и colocated.
PreventRequestForgery — новая CSRF-модель
CSRF-middleware переименован (VerifyCsrfToken → PreventRequestForgery) и стал двухуровневым:
- Origin-верификация по заголовку браузера
Sec-Fetch-Site— same-origin запрос пропускается без проверки токена. - Fallback на классический CSRF-токен (старый браузер или не-HTTPS — заголовок отправляется только по HTTPS).
php
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(
originOnly: true, // только origin-проверка, 403 вместо 419
allowSameSite: true, // разрешить поддомены (same-site, не только same-origin)
except: ['stripe/*'], // вебхуки
);
})Старые имена оставлены как deprecated-алиасы, но прямые ссылки в коде (withoutMiddleware([...])) обновить — правило уже в specs/laravel_13/testing.
Queue::route — централизованная маршрутизация очередей
Вместо ->onQueue() россыпью по коду — единая карта в AppServiceProvider::boot():
php
Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
Queue::route(RequiresVideo::class, queue: 'video'); // интерфейс — матчит все реализации
Queue::route([
ProcessPodcast::class => ['podcasts', 'redis'],
ProcessVideo::class => 'videos',
]);- Работает по классу, интерфейсу, трейту или родителю — задачи-наследники получают маршрут автоматически.
- Явный
->onQueue()/->onConnection()при dispatch приоритетнее правила. - Практика: карта очередей в одном месте = видно всю топологию воркеров при настройке supervisor/Horizon.
JSON:API Resources
Первопартийная реализация jsonapi.org — вместо ручной сборки структуры или сторонних пакетов:
shell
php artisan make:resource PostResource --json-apiphp
class PostResource extends JsonApiResource
{
public $attributes = ['title', 'body', 'created_at'];
public $relationships = ['author', 'comments']; // или 'author' => UserResource::class
}
return $post->toResource(); // единичный
return Post::all()->toResourceCollection(); // коллекцияИз коробки: sparse fieldsets (?fields[posts]=title), includes с dot-notation (?include=comments.author), lazy-атрибуты (closure вычисляется только если атрибут запрошен), Content-Type: application/vnd.api+json.
- Глубину includes ограничивать:
JsonApiResource::maxRelationshipDepth(3)в провайдере. - Relationships сериализуются только по
?include=— забытый eager loading не раздует ответ, но проверяй N+1 на include-путях. - Для парсинга входящих фильтров/сортировок официальная рекомендация — spatie/laravel-query-builder как companion.
Мелкие улучшения, о которых стоит знать
| Возможность | Применение |
|---|---|
Cache::touch($key, $seconds) | Продление TTL без чтения/перезаписи: один EXPIRE в Redis. Sliding-сессии, дешёвое «продление» горячих ключей |
middlewareFor() / withoutMiddlewareFor() | Точечный middleware на отдельные методы Route::resource() без разбиения на роуты (API 13.x) |
Route::softDeletableResources([...]) | Batch-регистрация ресурсов с withTrashed() |
| Приоритет доменных роутов | Роуты с явным ->domain() теперь матчатся раньше бездоменных — catch-all поддомены работают независимо от порядка регистрации |
Container::call() + nullable defaults | fn (?Carbon $date = null) через call() получит null, а не экземпляр — как constructor injection |
Апгрейд 12 → 13 — практика
Официальный путь: Boost /upgrade-laravel-v13, альтернативы — Laravel Shift (12.x→13.x, ~80-90% механики) и Rector-сет LaravelSetList::LARAVEL_130 (driftingly/rector-laravel).
«Тихие» риски, которых нет в коде приложения:
- Дефолтные префиксы сменились (
{app}_cache_→{app}-cache-,_session→-session). ЕслиCACHE_PREFIX/REDIS_PREFIX/SESSION_COOKIEне заданы явно в.env— деплой инвалидирует все сессии (разлогин юзеров) и кеш-ключи. До апгрейда снять текущие значения (config('cache.prefix'),config('session.cookie')в tinker) и закрепить в.env. symfony/polyfill-php85(новая зависимость) на PHP < 8.5 определяет глобальныеarray_first()/array_last()— конфликт сlaravel/helpersи кастомными хелперами с теми же именами (семантика другая: старый принимал callback). ИспользоватьArr::first()/Arr::last().- Имена view пагинации:
pagination::default→pagination::bootstrap-3— ловит только прямые ссылки на старые имена. - MySQL
DELETE+ JOIN + ORDER BY/LIMIT иupsert()безuniqueBy— теперь исключения (детали в specs/laravel_13/migrations).
Чеклист: PHP 8.3+ на сервере (единственный жёсткий блокер) → закрепить префиксы в .env → зелёные тесты как baseline → composer why-not laravel/framework:^13.0 (найти блокирующие пакеты) → composer update laravel/framework --with-all-dependencies → тесты. Путь с L10/L11 — только последовательно, версию за версией.
L12 получает багфиксы до 13.08.2026 и security до 24.02.2027 — апгрейд стабильного production не горит. Но новые проекты — только L13 (правило студии).
PHP 8.4/8.5 в коде Laravel 13
Официальная позиция: property hooks «clean up Eloquent accessors», asymmetric visibility «enables safer object design». Практика тоньше:
- Property hooks ≠ замена Eloquent accessors. Атрибуты модели идут через
__get()/__set()— hooks на БД-колонках просто не сработают. Правило: hooks — для чистых PHP-классов (DTO, Value Objects),Attribute::make()— для атрибутов моделей. Исключение: вычисляемые read-only свойства, не завязанные на колонку, — уже можно hook'ом вместо аксессора. - Hooks несовместимы с
readonly— если нужен контроль записи, брать asymmetric visibility:public protected(set) string $status. - DTO на PHP 8.4 — asymmetric visibility вместо геттеров-бойлерплейта:
php
final class OrderData
{
public function __construct(
public private(set) int $total, // читаемо снаружи, запись — только внутри
public private(set) OrderStatus $status,
) {}
}Экосистема под L13 (июль 2026)
| Пакет | Версия под L13 | Грабли |
|---|---|---|
| Filament | 5.x (мажор ради Livewire 4) | Требует Tailwind 4 — кастомные темы на v3 мигрировать сначала; vendor/bin/filament-v5 автоматизирует; плагины могут отставать |
| Livewire | 4.x | конфиг layout → component_layout; wire:model не ловит bubbling дочерних (нужен .deep); теги <livewire:...> обязаны закрываться />; wire:transition → View Transitions API |
| Larastan | 3.9.3+ | namespace larastan/larastan (не nunomaduro/) |
| Rector | driftingly/rector-laravel | сет LaravelSetList::LARAVEL_130; автодетект версии через LaravelSetProvider |
| PHPUnit / Pest | ^12.0 / ^4.0 | обязательные (см. specs/laravel_13/testing) |
| Boost / Tinker | ^2.0 / ^3.0 | обязательные |
Livewire/Inertia/Spatie-пакеты выпустили совместимые версии к релизу — но перед апгрейдом всегда composer why-not laravel/framework:^13.0.
Тестирование: что даёт Pest 4
Дополняет best/testing.md — здесь только L13-специфика (Pest 4 — требование L13):
- Browser testing встроен (Playwright под капотом, параллельно). Селекторы — текстовые/
data-test-хуки, не хрупкие CSS-цепочки. Browser-тесты шардировать отдельно от unit/feature — цикл обратной связи остаётся быстрым. - Mutation testing — метрика качества ассертов, а не покрытия:
shell
./vendor/bin/pest --mutate --parallel --covered-only --min=80 # порог для CIВыжившие мутанты = тесты проверяют только output, не edge cases. Точечный скоуп — covers(TodoController::class) в тесте. Требует XDebug 3+ или PCOV.
AI-агентная разработка на L13
Laravel официально затачивается «for Artisans and agents». Дополняет claude-code-workflow.md:
- Laravel PAO (dev-зависимость новых приложений по умолчанию) — детектирует, что PHPUnit/Pest/PHPStan/Artisan запущены AI-агентом, и отдаёт компактный JSON вместо прогресс-баров и ANSI-таблиц. Агент сразу видит failing assertion — меньше токенов, точнее правки. Для человека в терминале ничего не меняется.
- Boost 2: guidelines vs skills. Guidelines — всегда в контексте (базовые конвенции), skills — подгружаются по релевантности (узкие паттерны). Обновление —
php artisan boost:update --discover(можно наcomposer post-update-cmd). - Spatie guidelines-skills — стайлгайд Spatie как 4 skill-пакета (
spatie-laravel-php,spatie-securityи др.) для Claude Code/Cursor: агент без конфигурации «understandably doesn't use our coding style at all».
Anti-patterns (L13)
| Паттерн | Проблема | Решение |
|---|---|---|
| Новый проект на L12 | Багфиксы L12 кончаются 13.08.2026 | Стартовать на L13 |
Апгрейд без явных CACHE_PREFIX/SESSION_COOKIE в .env | Массовый разлогин + инвалидация кеша на деплое | Закрепить значения до апгрейда |
Глобальные хелперы array_first()/array_last() | Конфликт семантики с symfony/polyfill-php85 | Arr::first() / Arr::last() |
| Property hooks на БД-атрибутах модели | Не работают через __get()/__set() | Attribute::make(); hooks — для DTO/VO |
VerifyCsrfToken в новом коде | Deprecated-алиас | PreventRequestForgery |
| Атрибуты и классический синтаксис вперемешку | Двойное место истины, шум на ревью | Один стиль на проект (для L13 — атрибуты) |
->onQueue() россыпью по кодовой базе | Топологию очередей не видно целиком | Карта Queue::route() в провайдере |
| Апгрейд «через версию» (11 → 13) | Пропущенные breaking changes L12 | Только последовательно |