Плутанину навколо Laravel 11 створює слово «структура». Змінився шаблон нового проєкту, репозиторій laravel/laravel, тоді як сам фреймворк лишився де був: Illuminate\Foundation\Http\Kernel, Illuminate\Foundation\Console\Kernel і базовий обробник винятків нікуди не поділись. Прибрали їхні підкласи в app/, а разом із ними app/Http/Middleware/*, зайві сервіс-провайдери й теку lang. Натомість зʼявився bootstrap/app.php у новому вигляді: Application::configure() повертає ApplicationBuilder, а методи withRouting(), withMiddleware(), withExceptions(), withSchedule() накопичують налаштування й прикладають їх до ядра вже під час резолву через afterResolving(). Практичний висновок, за який на співбесіді ставлять плюс: існуючий застосунок після composer update до 11 працює зі старим app/Http/Kernel.php, а переїзд на білдер лишається добровільною роботою, яку роблять тоді, коли на неї є час.
Мапінг на код прямий. $middlewareGroups стає $middleware->web(append: [...]) і api(prepend: [...]), $middlewareAliases - викликом alias([...]), $middlewarePriority - priority([...]), а глобальний стек редагують через append(), prepend(), replace() і remove(). Винятки, які раніше лежали властивостями в успадкованих класах, тепер іменовані аргументи: validateCsrfTokens(except: [...]), encryptCookies(except: [...]), trustProxies(at: '*'), trustHosts(at: [...]), redirectGuestsTo('/login'). Обробник помилок розсипався на dontReport(), level(), report(), render(), context(), respond(). Планувальник переїхав у routes/console.php або в withSchedule(). Небезпечна комбінація одна: якщо в проєкті лишився власний app/Http/Kernel.php, а хтось паралельно почав писати withMiddleware(), виклики білдера тихо не застосуються, бо резолвиться ваш підклас, і alias «не реєструється» без жодної помилки.
Друга половина історії - конфіги, звідки й береться більшість запитань «а де воно тепер». Зі скелета 11 прибрали config/broadcasting.php, config/cors.php, config/hashing.php і config/view.php, лишивши десяток тих, які редагують щотижня. Значення при цьому не зникли: Illuminate\Foundation\Bootstrap\LoadConfiguration спершу читає базову конфігурацію з теки config пакета laravel/framework, а потім накриває її тим, що знайшов у вашій config/. Накриття відбувається на рівні цілого файлу. Опублікували cache.php через php artisan config:publish cache - і ваш масив тепер єдине джерело правди для ключа cache, включно з тим, що новий підключ із наступного мажора до вас не доїде. Тому --all виглядає зручно рівно один день, а далі перетворюється на пʼятнадцять файлів, які треба дифати вручну при кожному оновленні. Найдешевший спосіб змінити раунди bcrypt - змінна оточення, бо дефолтний файл фреймворка читає ті самі env(). Кешування конфігу працює як раніше: php artisan config:cache зберігає вже злитий результат.
Перейменовані змінні оточення тримайте в голові окремо, бо ламаються вони мовчки. CACHE_DRIVER став CACHE_STORE, старий ключ просто ігнорується, після чого продакшен їде на дефолтний драйвер без жодного попередження. Дефолти нового скелета теж інші: SQLite для бази, database для сесій, черг і кешу. Разом із цим у 11 прибрали залежність doctrine/dbal для зміни колонок, додали метод casts() замість властивості $casts, маршрут здоровʼя /up, обмеження швидкості з точністю до секунди й датовані нулями міграції 0001_01_01_000000_*, щоб ваші власні завжди йшли після базових. API- і broadcasting-стеки стали опційними: php artisan install:api створює routes/api.php, ставить Sanctum і додає api: у withRouting(), install:broadcasting - routes/channels.php і config/broadcasting.php.
Laravel 12 і 13 на цьому тлі навмисно нудні, і це правильна відповідь на питання «що там нового». Структура, задана в 11, лишилась базовою, bootstrap/app.php переписувати не довелося жодного разу, а зусилля команди пішли в стартер-кіти й дрібні зміни поведінки. Реальна робота при оновленні виглядає так: підняти мажори пакетів у composer.json (laravel/framework, phpunit/phpunit, laravel/sanctum, nunomaduro/collision, pestphp/pest), composer update --with-all-dependencies, обовʼязковий php artisan optimize:clear, далі прогін тестів. Те, що болить, зазвичай не в структурі: у 12 Carbon 3 став обовʼязковим, а правило валідації image перестало пропускати SVG без явного image:allow_svg, і це вилазить уже на продакшені, коли хтось вантажить логотип. Кожен наступний мажор підіймає мінімальну версію PHP і викидає те, що здепрековано в попередньому, тому стрибати через версію не варто: саме проміжний реліз показує попередження, які в наступному стануть фаталом. Звіряються тут з UPGRADE.md потрібної гілки laravel/framework і з php artisan about, який фіксує, що у вас насправді встановлено, а не з переказом релізних нотаток у чужому блозі.
// bootstrap/app.php - одна точка налаштування замість трьох Kernel-класів.
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
// routes/api.php у слім-скелеті немає, його створює php artisan install:api
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up', // готовий health-check для балансувальника
)
->withMiddleware(function (Middleware $middleware) {
// Колишній $middlewareGroups['web'] з app/Http/Kernel.php
$middleware->web(append: [ResolveTenant::class]);
// Колишній $middlewareAliases
$middleware->alias(['tenant' => ResolveTenant::class]);
// Замість властивості VerifyCsrfToken::$except у власному класі
$middleware->validateCsrfTokens(except: ['webhooks/stripe']);
// Замість app/Http/Middleware/TrustProxies.php
$middleware->trustProxies(at: '*');
})
->withExceptions(function (Exceptions $exceptions) {
// Колишній $dontReport з app/Exceptions/Handler.php
$exceptions->dontReport([TenantNotFound::class]);
$exceptions->level(QueryException::class, LogLevel::CRITICAL);
// Колишній register() зі старого хендлера
$exceptions->render(fn (TenantNotFound $e, Request $request) => $request->expectsJson()
? response()->json(['message' => 'Tenant not found'], 404)
: response()->view('errors.tenant', status: 404));
})
->withSchedule(function (Schedule $schedule) {
// Те саме можна писати в routes/console.php через фасад Schedule
$schedule->command('sitemap:build')->dailyAt('03:00')->withoutOverlapping();
})
->create();
Проведіть межу вголос: Laravel 11 змінив шаблон нового проєкту, а не фреймворк, тому питання «що робити зі старим Kernel» має відповідь «нічого, поки самі не захочете». Далі назвіть три конкретні речі, які реально болять при апдейті: перейменовані env-ключі (`CACHE_DRIVER` → `CACHE_STORE`), опубліковані конфіги, що відстали від апстріму, і зміни поведінки на кшталт SVG, вилученого з правила `image` у 12. Закінчіть процедурою: мажор за мажором, `UPGRADE.md` гілки, зелений CI між кроками.