iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
Modern Web

Laravel 那麼好用還需要自幹框架嗎系列 第 6

Day 06:Laravel 如何引入路由檔案

  • 分享至 

  • xImage
  •  

我們取得了使用者輸入的請求之後,再來就是如何將使用者的請求導向正確的位址。

在 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 是怎麼定義路由與解析路由的。


上一篇
Day 05:引用 Symfony 並更新框架核心
下一篇
Day 07:Laravel 如何建立路由物件
系列文
Laravel 那麼好用還需要自幹框架嗎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言