«Старый свет погибает с теми, кто не способен понять новое». Mr. Freeman, part 62
# Что произошло?
Laravel 11 вышел в марте 2024 года и в отличии от 6 - 10 версий, которые не сильно отличались друг от друга, 11-ая получила обновленную структуру scaffold-а. Вся суть обновления заключается в минималистичности стартового приложения и некоторые привычные моменты были спрятаны под "капот". Это коснулось конфигурации, мидлварок, планировщика, маршрутов, обработчика ошибок и сервис-провайдеров.
Функционал фреймворка работает как и прежде, ничего важного не удалили и не добавили. Но если ты привык использовать предыдущие версии, то в 11-ой у тебя скорей всего возникнут вопросы как сделать привычные вещи или почему что-то стало работать не так.
# Обновление до 11
Согласно «Upgrade Guide» не рекомендуется переносить новую структуру 11-ой версии в существующее приложение, т.к. сохранена полная совместимость.
Это не значит, что обновляться до 11-ой версии не рекомендуется. Речь идет именно за структуру (файлы/классы и директории), поэтому можно сохранить текущую структуру приложения.
Конечно, в дальнейшем это может создать двойственность и запутать начинающих разработчиков без опыта в предыдущих версиях, но так ли это важно? Обновление структуры в существующем проекте может потребовать много усилий и не дать соразмерного профита, но в перспективе будет проще обновляться до новых версий.
Для обновления с 10-ой версии необходимо ознакомиться с «Upgrade Guide» , а также можно воспользоваться GitHub comparison tool для более детального изучения изменений в исходном коде. Обновление происходит так же, как и во всех предыдущих версиях.
# Что изменилось?
Пересказывать «Release Notes» не имеет смысла, в них отражены все важные моменты. Мы же разберем популярные и неочевидные проблемы, с которыми столкнулись разработчики.
# Основные моменты
-
Содержимое app после установки:
App\Http\Controllers\Controller App\Models\User App\Provider\AppServiceProvider
- Удалено: app/Exceptions app/Console app/Http/Middleware app/Http/Kernel.php lang
- Регистрация сервис-провайдеров перенесена из конфига config/app.php в файл bootstrap/providers.php
- Регистрация middleware перенесена из app/Http/Kernel.php в файл bootstrap/app.php
- Обработчик ошибок перенесен из app/Exceptions/Handler.php в файл bootstrap/app.php
- Планировщик (cron) перенесен из app/Console/Kernel.php в файл bootstrap/app.php
# Service Providers
Регистрация провайдеров в config/app.php всё еще доступна, но по умолчанию была перемещена в bootstrap/providers.php. В массиве указываются свои провайдеры, например, AppServiceProvider. Альтернатива: передавать массив с помощью метода withProviders() в bootstrap/app.php.
Для удаления дефолтных провайдеров фреймворка необходимо прибегнуть к старому способу через конфиг, с помощью ServiceProvider::defaultProviders()->except().
Стоит отметить, что были удалены многие провайдеры и все регистрации предлагается совершать через App\Providers\AppServiceProvider. С одной стороны это сосредоточило логику настройки приложения, с другой может сильно загрязнить провайдер. Конечно, никто не запрещает создать любые необходимые провайдеры и подключить их, тем самым распределив ответственность.
# Middleware
Все middleware были перемещены без изменений в \Illuminate\Foundation\Configuration\Middleware.
Согласно документации , управление осуществляется в методе withMiddleware().
Для этого в распоряжении есть объект
\Illuminate\Foundation\Configuration\Middleware ,
со всеми необходимыми методами:
append,
prepend,
remove,
replace,
group,
use и т.п.
С полным списком можно ознакомиться
в API
или в документации .
Пример, как это выглядит в 11 версии:
1use Illuminate\Foundation\Configuration\Middleware; 2 3Application::configure() 4 ->withMiddleware(function (Middleware $middleware) { 5 $middleware->redirectTo( 6 guests: fn () => route('login'), 7 users: fn () => route('dashboard'), 8 ); 9 10 $middleware->encryptCookies(except: [11 'name',12 ]);13 14 $middleware->preventRequestsDuringMaintenance(except: [15 '/horizon*',16 ]);17 18 $middleware->statefulApi(); // для Sanctum SPA19 $middleware->throttleApi(); // throttle по умолчанию выключен20 21 /* Вместо старого свойства $middleware */22 $middleware->append([23 GlobalMiddleware::class,24 ]);25 26 /* Вместо старого свойства $middlewareGroups */27 $middleware->web(append: [28 WebMiddleware::class,29 ]);30 31 $middleware->api(remove: [32 SubstituteBindings::class,33 ]);34 35 /* Вместо старого свойства $middlewareAliases */36 $middleware->alias([37 'custom-alias' => CustomMiddleware::class,38 ]);39 })
# Router
App\Providers\RouteServiceProvider удален и настройки производятся в withRouting() , где указываются абсолютные пути к файлам маршрутов через аргументы: web, api, channels, commands.
По умолчанию API маршруты не подключены, установить можно с помощью команды php artisan install:api, смотри документацию .
Префикс для API маршрутов можно изменить с помощью аргумента: apiPrefix
Route::pattern() и RateLimiter::for() можно определить в AppServiceProvider::boot().
# Exceptions
Обработка исключений перемещена из App/Exceptions/Handler в withExceptions().
Глобально ничего не изменилось, принцип такой же как с middleware, доступны все необходимые методы в \Illuminate\Foundation\Configuration\Exceptions .
Подробнее смотри документацию или пример в секции «Наводим порядок».
# Redirect
За переадресацию пользователей раньше отвечали отдельные middleware, теперь за это отвечает метод $middleware->redirectTo(), который принимает два аргумента:
guests - для переадресации c auth маршрутов, если пользователь не аутентифицирован.
users - для переадресации с гостевых маршрутов, если пользователь аутентифицирован.
Оба аргумента могут принимать callback, в который передается Request, это может быть полезно для переадресации с учетом запроса. Пример смотри выше в секции «Middleware».
Обрати внимание, что callback должен возвращать строку, путь куда будет перенаправлен пользователь. Возвращать Response нельзя.
Так же стоит отметить, что для не аутентифицированных пользователей фреймворк самостоятельно вызовет $request->expectsJson() и для запросов ожидающих json редиректа не будет. Но для аутентифицированных, такого поведения нет и всегда будет редирект, что может оказаться нежелательным поведением для API, поэтому, возможно, понадобится дополнительная проверка в callback.
# Casts
Изменился способ определения кастов полей в моделях. Старый способ через свойство protected array $casts; доступен тоже. В новом способе предлагается использовать метод protected function casts(): array, который возвращает массив (формат массива не изменился). Подробнее смотри в документации .
Обрати внимание, что при одновременном использовании двух способов, массивы сливаются и ключи перечисленные в методе casts() могут перетереть ключи из массива в свойстве $casts, поэтому рекомендую выбрать один способ и придерживаться его.
# Throttling
По умолчанию RateLimiter теперь отключен для API маршрутов.
Для включения необходимо вызвать $middleware->throttleApi(); в withMiddleware().
Пример смотри в секции «Middleware».
# CORS
Конфиг config/cors.php по умолчанию отсутствует. Для публикации конфига используй команду: php artisan config:publish cors
Подробнее смотри в документации .
# Service Aliases
Хоть альясы сервисов и не являются рекомендательной практикой, но если использовались, то в 11 версии альясы можно определить в AppServiceProvider
1use Illuminate\Foundation\AliasLoader;2 3public function register(): void4{5 $aliasLoader = AliasLoader::getInstance();6 $aliasLoader->alias('CustomService', CustomService::class);7}
# Наводим порядок
Изменения в 11 версии были восприняты сообществом неоднозначно. В основном из-за подхода к настройке middleware, exceptions, scheduler. Всё было вынесено в bootstrap/app.php, что в реальном проекте сильно "раздувает" код и выглядит лаконично только в документации.
Есть очень простое решение, как можно "вернуть" старую структуру не отказываясь от новой.
Методы withExceptions, withMiddleware, withSchedule и т.п. принимают тип callable, а значит мы можем передать invokable объект, тем самым вынеся логику в привычные и отдельные классы.
Создаем классы с магическим методом __invoke(), имена и расположение могут быть любыми, и переносим всю логику из callback.
App\Exceptions\ExceptionHandler
1namespace App\Exceptions; 2 3use Illuminate\Foundation\Configuration\Exceptions; 4 5final readonly class ExceptionHandler 6{ 7 public function __invoke(Exceptions $exceptions): void 8 { 9 $exceptions->dontReport([10 SilentException::class,11 ]);12 13 $exceptions->dontFlash([14 'secret_key',15 ]);16 17 $exceptions->reportable(function (Throwable $e) {18 Integration::captureUnhandledException($e);19 });20 21 $exceptions->render(function (AppException $exception, Request $request) {22 // any logic23 });24 }25}
App\Console\ScheduleHandler
1namespace App\Console; 2 3use Illuminate\Console\Scheduling\Schedule; 4 5final readonly class ScheduleHandler 6{ 7 public function __invoke(Schedule $schedule): void 8 { 9 $schedule10 ->command(SendEmailCommand::class)11 ->withoutOverlapping()12 ->everyTenMinutes();13 }14}
App\Http\Middleware\MiddlewareHandler
1namespace App\Http\Middleware; 2 3use Illuminate\Foundation\Configuration\Middleware; 4 5final readonly class MiddlewareHandler 6{ 7 public function __invoke(Middleware $middleware): void 8 { 9 $middleware->redirectTo(10 guests: fn () => route('login'),11 );12 13 $middleware->statefulApi();14 $middleware->throttleApi();15 }16}
Применяем классы в bootstrap/app.php
1use App\Console\ScheduleHandler; 2use App\Exceptions\ExceptionHandler; 3use App\Http\Middleware\MiddlewareHandler; 4use Illuminate\Foundation\Application; 5 6return Application::configure(basePath: dirname(__DIR__)) 7 ->withExceptions(new ExceptionHandler) 8 ->withMiddleware(new MiddlewareHandler) 9 ->withSchedule(new ScheduleHandler)10 ->withRouting(11 web: __DIR__ . '/../routes/web.php',12 api: __DIR__ . '/../routes/api.php',13 )14 ->create();
Технически ничего не изменилось, но логика была распределена по своим классам и bootstrap/app.php стал выглядеть намного чище и проще для восприятия.