我們取得了使用者輸入的請求之後,再來就是如何將使用者的請求導向正確的位址。
在 Laravel 11 裡面,首先我們會先看到 bootstrap/app.php
裡面有著
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();
這裡我們先關注跟路由相關的部分,configure()
和 withMiddleware()
之類的邏輯之後有機會再來處理。
在這邊使用 withRouting()
引入了 routes/web.php
之後,在這個檔案裡面定義路由。
Laravel 是怎麼實作這段邏輯的呢?我們一起來看看。
首先我們看看 withRouting()
的實作
public function withRouting(?Closure $using = null,
array|string|null $web = null,
array|string|null $api = null,
?string $commands = null,
?string $channels = null,
?string $pages = null,
?string $health = null,
string $apiPrefix = 'api',
?callable $then = null)
{
if (is_null($using) && (is_string($web) || is_array($web) || is_string($api) || is_array($api) || is_string($pages) || is_string($health)) || is_callable($then)) {
$using = $this->buildRoutingCallback($web, $api, $pages, $health, $apiPrefix, $then);
}
AppRouteServiceProvider::loadRoutesUsing($using);
$this->app->booting(function () {
$this->app->register(AppRouteServiceProvider::class, force: true);
});
if (is_string($commands) && realpath($commands) !== false) {
$this->withCommands([$commands]);
}
if (is_string($channels) && realpath($channels) !== false) {
$this->withBroadcasting($channels);
}
return $this;
}
這邊我們的 web
提供的是一個陣列,所以會進到 buildRoutingCallback()
。
buildRoutingCallback()
的實作則是
protected function buildRoutingCallback(array|string|null $web,
array|string|null $api,
?string $pages,
?string $health,
string $apiPrefix,
?callable $then)
{
return function () use ($web, $api, $pages, $health, $apiPrefix, $then) {
if (is_string($api) || is_array($api)) {
if (is_array($api)) {
foreach ($api as $apiRoute) {
if (realpath($apiRoute) !== false) {
Route::middleware('api')->prefix($apiPrefix)->group($apiRoute);
}
}
} else {
Route::middleware('api')->prefix($apiPrefix)->group($api);
}
}
if (is_string($health)) {
Route::get($health, function () {
Event::dispatch(new DiagnosingHealth);
return View::file(__DIR__.'/../resources/health-up.blade.php');
});
}
if (is_string($web) || is_array($web)) {
if (is_array($web)) {
foreach ($web as $webRoute) {
if (realpath($webRoute) !== false) {
Route::middleware('web')->group($webRoute);
}
}
} else {
Route::middleware('web')->group($web);
}
}
if (is_string($pages) &&
realpath($pages) !== false &&
class_exists(Folio::class)) {
Folio::route($pages, middleware: $this->pageMiddleware);
}
if (is_callable($then)) {
$then($this->app);
}
};
}
這邊有關 $web
的邏輯是這段
if (is_string($web) || is_array($web)) {
if (is_array($web)) {
foreach ($web as $webRoute) {
if (realpath($webRoute) !== false) {
Route::middleware('web')->group($webRoute);
}
}
} else {
Route::middleware('web')->group($web);
}
}
可以看到,這一段的邏輯是如果輸入是一個陣列,就依次執行
Route::middleware('web')->group($webRoute);
也就是說,最終註冊路由的地方,還是使用 Route::group()
這個函數
我們繼續看 group()
的實作
/**
* Create a route group with shared attributes.
*
* @param array $attributes
* @param \Closure|array|string $routes
* @return $this
*/
public function group(array $attributes, $routes)
{
foreach (Arr::wrap($routes) as $groupRoutes) {
$this->updateGroupStack($attributes);
// Once we have updated the group stack, we'll load the provided routes and
// merge in the group's attributes when the routes are created. After we
// have created the routes, we will pop the attributes off the stack.
$this->loadRoutes($groupRoutes);
array_pop($this->groupStack);
}
return $this;
}
這邊的註解告訴我們,更新了 group stack 之後,就會使用 loadRoutes()
來加入新的路由
loadRoutes()
的實作如下:
/**
* Load the provided routes.
*
* @param \Closure|string $routes
* @return void
*/
protected function loadRoutes($routes)
{
if ($routes instanceof Closure) {
$routes($this);
} else {
(new RouteFileRegistrar($this))->register($routes);
}
}
由於我們前面宣告的 $web
是包含許多檔名的一個陣列,在這邊我們會進入到 (new RouteFileRegistrar($this))->register($routes);
的邏輯
這段的實作非常簡單,只有短短幾行
/**
* Require the given routes file.
*
* @param string $routes
* @return void
*/
public function register($routes)
{
$router = $this->router;
require $routes;
}
到這邊我們就可以知道,Laravel 在最後,是依次根據陣列內的檔名來 require
檔案,引入 $web
內列出的所有路由檔案。
今天我們就先看到這邊,明天我們再來看看 Laravel 是怎麼定義路由與解析路由的。