<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
LARAVEL · SENIOR ЧАСТО ПИТАЮТЬ

Що змінилось у Laravel 11-13: слімова структура, конфіг, що прибрали й що додали?

Laravel 11 прибрав з нового скелета `Http/Kernel`, `Console/Kernel`, `Exceptions/Handler`, половину конфігів і теку `lang`, перенісши налаштування в `bootstrap/app.php` і в дефолти фреймворка; 12 і 13 структуру не чіпали, тож оновлення між ними - це бамп версій у `composer.json` плюс вичитаний `UPGRADE.md`, а не переписування застосунку.

Оновили проєкт з 10 на 11, а `app/Http/Kernel.php` лишився на місці. Це поламаний апдейт чи так і має бути?
Куди в Laravel 11 писати `$middlewareGroups` і `$routeMiddleware`, якщо Kernel-класу немає?
У новому проєкті немає `config/hashing.php`. Звідки тоді береться `bcrypt` і як підняти кількість раундів?
Після оновлення до 11 кеш поїхав у файли, хоча в `.env` стоїть `CACHE_DRIVER=redis`. Чому?
Laravel 11 Laravel 12 Laravel 13 bootstrap/app.php config:publish оновлення

Плутанину навколо 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();
Що Kernel-класи нікуди не зникли: `Illuminate\Foundation\Http\Kernel` і `Illuminate\Foundation\Console\Kernel` живуть у фреймворку, а видалили лише їхні підкласи в `app/`; `withMiddleware()` і `withExceptions()` просто передають налаштування цим об'єктам через `afterResolving()`.
Що слімовий скелет стосується `laravel/laravel` (шаблон нового проєкту), а не `laravel/framework`: старий застосунок після `composer update` працює з власним `app/Http/Kernel.php` і переїзд на `bootstrap/app.php` - окрема добровільна робота.
Що відсутній `config/cache.php` не означає відсутніх налаштувань: `LoadConfiguration` підставляє дефолт фреймворка, а опублікований файл замінює його цілком, а не зливається ключ за ключем.
Що `config:publish` варто робити точково (`php artisan config:publish cache`), бо кожен опублікований файл - це ваш обов'язок стежити за новими ключами під час наступних мажорів.
Що між 11 і 12 і далі до 13 структура не змінювалась, а реальна робота при оновленні - це PHP-вимога, видалені deprecated-методи й зміни поведінки на кшталт SVG у правилі `image`.
Що версійно-специфічний список береться з `UPGRADE.md` потрібної гілки `laravel/framework`, а не з памʼяті й не з блогу.
Вважати, що оновлення до 11 вимагає видалити `app/Http/Kernel.php`: фреймворк його поважає, а видалення без перенесення груп middleware ламає CSRF і сесії на всьому `web`.
Тримати одночасно старий `app/Http/Kernel.php` і `withMiddleware()` у `bootstrap/app.php`: викликів у білдері ніхто не застосує, бо резолвиться ваш підклас, і півдня йде на пошук «чому alias не реєструється».
Після 11 лишити в `.env` `CACHE_DRIVER=redis`: ключ перейменували на `CACHE_STORE`, старий просто ігнорується, і застосунок мовчки їде на дефолтний драйвер.
Робити `php artisan config:publish --all` «щоб було як раніше»: через рік це 15 файлів, що відстали від апстріму, і саме вони ламають наступний мажор.
Шукати `routes/api.php` у свіжому проєкті й створювати його руками замість `php artisan install:api` - без Sanctum, міграції токенів і параметра `api:` у `withRouting()` половина API-стека просто не підключена.
Оновлювати 11 → 13 одним стрибком через `"laravel/framework": "^13.0"`: пропущений цикл deprecation означає, що ви одночасно ловите зміни двох мажорів без проміжного зеленого тесту.
Ставити важку логіку в замикання `withMiddleware()` чи `withExceptions()`: код виконується на кожному запиті під час бутстрапа й не кешується `php artisan optimize`.
ПОРАДА

Проведіть межу вголос: Laravel 11 змінив шаблон нового проєкту, а не фреймворк, тому питання «що робити зі старим Kernel» має відповідь «нічого, поки самі не захочете». Далі назвіть три конкретні речі, які реально болять при апдейті: перейменовані env-ключі (`CACHE_DRIVER` → `CACHE_STORE`), опубліковані конфіги, що відстали від апстріму, і зміни поведінки на кшталт SVG, вилученого з правила `image` у 12. Закінчіть процедурою: мажор за мажором, `UPGRADE.md` гілки, зелений CI між кроками.

оновлено 20 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Слімовий скелет - це зміна шаблону `laravel/laravel`, а не фреймворка: контракти `Http\Kernel` і `Console\Kernel` як біндились на реалізації з `Illuminate\Foundation`, так і біндяться, тому застосунок зі своїм підкласом після `composer update` живий (і саме тому виклики `withMiddleware()` у ньому не спрацюють). `LoadConfiguration` бере базову конфігурацію з теки `config` самого фреймворка й накриває її вашими файлами на рівні цілого файлу, тож `hashing.rounds` чудово керується змінною оточення без публікації, а опублікована копія перестає отримувати нові ключі з апстріму. Формат `bootstrap/app.php`, заданий у 11, лишився незмінним у 12 і 13.