iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Modern Web

我推的Laravel S2!系列 第 6

我推的Laravel S2|Day 05:全新應用程式骨架:bootstrap/app.php 與目錄結構

  • 分享至 

  • xImage
  •  

一句話破題

打開 blog-appapp/Http 目錄,你會發現找不到 Kernel.php;打開 app/Exceptions,這個目錄根本不存在;app/Console 也是空的。如果你是照著 Laravel 10 時代的教材學習,這一刻大概會愣住——別擔心,你的專案沒有壞掉,這是 Laravel 11 起最大幅度的一次骨架重構。目錄結構通常是放在最後當附錄的內容,但這個改動影響太大,提前到 Day 5 講清楚,讓你接下來 25 天都用對心智模型。

背景:舊骨架長什麼樣子

Laravel 10 時代的結構是這樣分工的:app/Http/Kernel.php 管中介層、app/Console/Kernel.php 管排程與自訂指令、app/Exceptions/Handler.php 管例外處理,加上五個預設 Service Provider 各自註冊不同的東西。這套用了很多年,網路上絕大多數教學文章都是這樣教的。

Laravel 11 官方的說法是:「新應用程式結構是為新專案設計的,我們不建議既有的 Laravel 10 專案跟著改結構」——也就是說,如果你手上有舊專案,它不會被強迫升級目錄結構,一樣能繼續在 Laravel 11+ 運作。但既然這系列的 blog-app 是全新建立的專案,從今天開始你看到的就是全新骨架,之後 Middleware、Exception、Schedule、Request Lifecycle 這幾天的內容,全部都會基於這個新骨架來講。

為什麼要做這次重構:官方的動機

理解「為什麼」通常比死記「現在該怎麼寫」更能幫助你在遇到新情境時自己判斷。Laravel 團隊公開談過這次重構的動機,大致可以歸納成兩點:

  1. 降低新專案的認知負擔。一個全新的 Laravel 10 專案,app/Http/Middleware 目錄預設就有九個檔案,多數初學者根本不知道每個檔案在做什麼、為什麼要存在,這些「預先鋪好但你可能一輩子不會碰」的檔案,某種程度上是不必要的學習成本。Laravel 11 把它們全部收進框架本體,你的專案目錄裡只留下你真正動過手的東西。
  2. 把設定的「發現性」(discoverability)集中化。舊骨架裡,一個中介層的行為要橫跨 Kernel.php 好幾個陣列才能拼湊出完整圖像;新骨架把同一個關注點(中介層、例外處理、排程)的設定都收斂到一個檔案的一個方法呼叫裡,減少「這個設定到底寫在哪」的搜尋成本。

舊骨架把中介層、排程、例外處理、Provider 分散在四個檔案;新骨架全部收斂進 bootstrap/app.php 的鏈式呼叫

核心變化:bootstrap/app.php 變成一切的起點

新骨架最核心的改變,是 bootstrap/app.php 從一個「載入框架的樣板檔案」變成一個「程式碼優先(code-first)的應用程式設定檔」。打開這個檔案,你會看到類似這樣的結構:

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

這一個檔案現在集中設定了:路由檔案的位置、中介層行為、例外處理邏輯。以前分散在 Kernel.phpHandler.php 好幾個檔案裡的設定,現在都收斂到這裡。今天先建立整體印象,withMiddleware()withExceptions() 的細節分別留給 Day 16、Day 18 深入。

withRouting() 的完整參數

上面範例只用了 web/commands/health 三個參數,實際上 withRouting() 還接受更多選項,值得一次認識完整:

->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    channels: __DIR__.'/../routes/channels.php',
    health: '/up',
    then: function () {
        // 在上面所有路由檔案都載入完成之後,還想額外註冊一些路由
        Route::middleware('web')->get('/custom-page', function () {
            return view('custom');
        });
    },
)

apichannels 兩個參數,只有在你實際執行過 php artisan install:api/install:broadcasting(下一節會提到)之後才會出現在這個方法呼叫裡——一個全新的 blog-app 骨架預設只有 web/commands/health 三個參數,這正好呼應「新專案不背負用不到的檔案」這個設計理念。then 這個閉包參數是給那種「不方便放進獨立路由檔案、但又想確保在所有標準路由之後才註冊」的邊角情境用的,多數專案不會用到。

Health Routing:一行設定換一個健康檢查端點

上面範例裡的 health: '/up' 是 Laravel 11 新增的功能:只要在 withRouting() 裡指定這個參數,Laravel 就會自動註冊一個健康檢查路由(預設 /up),你可以直接把這個網址交給 Kubernetes、Uptime 監控服務等外部系統做存活檢查。每次這個路由被請求時,Laravel 還會 dispatch 一個 DiagnosingHealth 事件,你可以監聽這個事件加入自訂的健康檢查邏輯(例如檢查資料庫連線、Redis 連線是否正常):

// app/Providers/AppServiceProvider.php
use Illuminate\Foundation\Events\DiagnosingHealth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (DiagnosingHealth $event) {
        DB::connection()->getPdo();   // 連不上資料庫會直接拋出例外,讓健康檢查失敗
    });
}

這個模式在 Day 29 部署 blog-app 到 Render 時會實際派上用場——雲端平台通常會定期打這個端點確認你的應用程式還活著,如果資料庫連線斷了卻只回應「200 OK」,平台不會知道你的應用程式實際上已經不能正常服務。

Service Provider 大瘦身

Laravel 10 的新專案預設會有五個 Service Provider:AppServiceProviderAuthServiceProviderEventServiceProviderRouteServiceProviderBroadcastServiceProvider。打開 blog-appapp/Providers 目錄,現在只剩一個 AppServiceProvider

這不是說其他 Provider 的功能消失了,而是被拆到三個地方:

  1. 框架自動處理:例如事件監聽器現在會自動探索(event discovery)——只要你的 Listener 類別命名跟慣例相符,Laravel 會自動找到並註冊,多數情況不需要再手動維護一份事件對應表。
  2. 整合進 bootstrap/app.php:路由檔案位置(原本 RouteServiceProvider 的工作)現在寫在 withRouting() 裡。
  3. 寫進 AppServiceProvider:像原本會放在 AuthServiceProvider 的 Gate/Policy 註冊,或原本放在 RouteServiceProvider 裡的 RateLimiter 定義(Day 7 會示範),現在都寫進 AppServiceProvider::boot()

Event Discovery 實際怎麼運作

第一點提到的「事件自動探索」值得展開講清楚,因為它是這次瘦身能夠成立的關鍵機制之一。以前你需要在 EventServiceProvider 手動維護一份對應表:

// 舊寫法(EventServiceProvider 已不存在,僅供對照)
protected $listen = [
    PostPublished::class => [
        SendPostPublishedNotification::class,
    ],
];

現在只要你的 Listener 類別放在 app/Listeners 目錄下,並且用型別提示宣告它監聽哪個事件,Laravel 啟動時會自動掃描並註冊,不需要任何手動維護的對應表:

// app/Listeners/SendPostPublishedNotification.php
class SendPostPublishedNotification
{
    public function handle(PostPublished $event): void
    {
        // ...
    }
}

Laravel 掃描的依據是 handle() 方法的參數型別提示——只要它是一個型別提示為某個事件類別的公開方法,就會被自動註冊。如果你偏好明確列出對應關係(例如團隊規範要求所有事件/監聽器綁定都要能一眼看到清單,不依賴掃描機制),可以在 AppServiceProvider::boot() 手動呼叫 Event::listen(),兩種方式可以並存,自動探索只是「預設行為」而不是「唯一選項」。正式環境建議執行 php artisan event:cache 快取掃描結果,避免每次請求都重新掃描檔案系統帶來的效能開銷。

新增 Service Provider 要註冊在哪?

如果你之後用 php artisan make:provider 建立自己的 Service Provider,它會自動被加進 bootstrap/providers.php(一個回傳陣列的簡單檔案),不是舊版的 config/app.php 裡的 providers 陣列:

<?php
// bootstrap/providers.php

return [
    App\Providers\AppServiceProvider::class,
];

config/app.phpproviders 陣列寫法目前仍然相容(向下相容機制),但新專案的慣例已經改成這個獨立檔案。Day 23 講 Service Provider 時會再深入示範怎麼建立自己的 Provider。

這個檔案還有一個實務上好用的特性:因為它就是一個回傳純陣列的 PHP 檔案,你可以用一般的 PHP 條件邏輯決定要不要註冊某個 Provider,例如只在本機環境載入除錯用的 Provider:

<?php
// bootstrap/providers.php

return array_filter([
    App\Providers\AppServiceProvider::class,
    app()->environment('local') ? App\Providers\TelescopeServiceProvider::class : null,
]);

路由檔案:從「都在」變成「按需啟用」

routes/web.phproutes/console.php 依然是預設就有的兩個檔案,但 routes/api.phproutes/channels.php 在新專案裡預設不存在,因為並不是每個專案都需要 API 路由或事件廣播。需要的話用指令按需建立:

php artisan install:api          # 建立 routes/api.php,同時會安裝 Sanctum
php artisan install:broadcasting # 建立 routes/channels.php

blog-app 目前是純 Blade/Livewire 應用,暫時不需要這兩個檔案;如果後面章節(例如 Day 14 RESTful API)要示範對外 API,屆時再用 install:api 補上。

完整目錄結構走一遍

十個根目錄各自的職責,以及相對 Laravel 10 的變化:

blog-app 目錄結構樹狀圖:bootstrap/app.php 是新骨架中樞,app/ 目錄預設只剩 Http、Models、Providers 三個子目錄,routes/api.php 與 channels.php 改為按需產生

目錄 用途 相對 Laravel 10 的變化
app/ 應用程式核心程式碼 預設只剩 HttpModelsProviders 三個子目錄,其餘(ConsoleEventsExceptionsJobsListenersMailNotificationsPoliciesRules都是按需產生,跑對應的 make:* 指令才會出現
bootstrap/ 框架啟動檔案 新增 app.php 作為中樞設定檔、providers.php 註冊自訂 Provider;cache/ 存放路由快取等效能優化檔案
config/ 設定檔 結構不變,但部分原本放在 config/app.php 的職責(如 providers 陣列)已轉移
database/ Migration、Factory、Seeder 不變;新專案預設用 SQLite,這個目錄也可以拿來放 SQLite 資料庫檔
public/ 對外根目錄、index.php 入口 不變
resources/ Views、前端原始碼 不變,但內容視你選的 Starter Kit 而定(Blade+Livewire 或 Inertia+React/Vue/Svelte)
routes/ 路由定義 api.php/channels.php 改為按需產生;console.php 除了定義指令,現在也是排程定義的位置(Day 25 會示範)
storage/ Log、快取、Session 等產生檔案 不變
tests/ 自動化測試 不變,預設同時提供 Pest 範例
vendor/ Composer 相依套件 不變

app/ 目錄的變化是最需要重新建立印象的地方:以前一個全新專案打開 app/Http 就會看到一堆預先建好的 Middleware 檔案,現在是乾淨的,你用到什麼才產生什麼。想知道有哪些 make:* 指令可以產生對應目錄,執行 php artisan list make 就能看到完整清單。

bootstrap/cache/ 到底存了什麼

這個子目錄容易被忽略,但實際上是效能優化機制的落腳點。當你執行 php artisan config:cache(Day 6 會講)、route:cache(Day 7 會講)這類快取指令,產生出來的合併檔案就是存在這裡。它不應該被提交進版本控制(.gitignore 預設已經排除),因為這些是「根據你目前的設定產生出的衍生檔案」,不是原始碼本身——正式環境部署流程通常會在部署腳本裡重新執行這些快取指令,而不是依賴把快取檔案本身搬到伺服器上。

常見錯誤與踩雷點

  • 照著舊教材去找 app/Http/Kernel.php 想加中介層,結果找不到檔案:這是這次改版影響最大的地方,正確位置是 bootstrap/app.phpwithMiddleware(),Day 16 會完整示範。
  • 升級舊專案時,看到新版文件就想把目錄結構「升級」成新樣式:官方明確建議不要這樣做。Laravel 11+ 的執行環境同時相容新舊兩種骨架,你的 Laravel 10 專案不需要為了跟上文件而搬家,除非你有具體理由(例如想精簡專案)。
  • bootstrap/app.phpconfig/app.php 搞混:兩個檔案功能完全不同,config/app.php 還是放應用程式基本設定(時區、語系等),bootstrap/app.php 是新的中樞啟動設定檔,命名相近但職責不同,容易看錯。
  • 手動建立 Service Provider 檔案,卻忘記註冊進 bootstrap/providers.php:如果你是複製貼上建立 Provider 檔案(而不是用 make:provider 指令),這個檔案不會自動被加進去,register()/boot() 就完全不會被執行,而且通常不會有明顯的錯誤訊息,只是功能悄悄地沒生效,排查起來容易花冤枉時間。
  • 以為 install:api 只是建立一個空檔案:這個指令同時會安裝並設定 Sanctum(Day 14、Day 28 會用到),如果你的專案已經有自己的 API Token 認證機制,執行前先確認不會跟既有設定衝突。

小結

Laravel 11 的骨架重構把分散在 Kernel.phpHandler.php、五個 Service Provider 裡的設定,收斂進 bootstrap/app.php 一個檔案,並讓 app/ 目錄下多數子目錄改為按需產生。今天也認識了 withRouting() 的完整參數、事件自動探索的運作原理、以及 bootstrap/cache/ 這個容易被忽略的效能優化落腳點。這不是砍掉功能,是把「新專案預設要背負的檔案數量」降到最低,同時保留完全相容既有 Laravel 10 專案的彈性。接下來的章節只要提到 Middleware、Exception、Schedule,都會以今天建立的心智模型為準。

萬丈高樓平地起 — 中國諺語

明日預告

Day 6 回到相對穩定的地帶:.env 環境變數、Config 系統、Debug 與維護模式,這塊在 10 到 13 之間幾乎沒有破壞性變動,但還是有幾個容易忽略的實用細節值得覆盤。


上一篇
我推的Laravel S2|Day 04:用 Starter Kit 快速起手:從 Breeze/Jetstream
下一篇
我推的Laravel S2|Day 06:環境變數與 Config:.env 加密、Debug、維護模式
系列文
我推的Laravel S2!7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言