はじめに
laravelではルーティングの定義をroutes/web.php
などに記載しますが、どのようにそれが読み込まれてどこに情報が保持されるのかをソースコードを読んで追っていきたいと思います。
Routeファサード
Routeファサードで解決されるクラスIlluminate/Routing/Router.php
public function __construct(Dispatcher $events, Container $container = null)
{
$this->events = $events;
$this->routes = new RouteCollection;
$this->container = $container ?: new Container;
}
routerのバインドを書いているサービスプロバイダーIlluminate/Routing/RoutingServiceProvider
はlaravelのboot時に Illuminate/Foundation/Application.php
のコンストラクタで登録されている。
Let's Go
Illuminate/Foundation/Http/Kernel::sendRequestThroughRouter()
内の$this->bootstrap()
でconfig/app.php
に記述されているサービスプロバイダーが登録 and 起動されます。
ルートに関わるサービスプロバイダーはApp\Providers\RouteServiceProvider
で、このクラスはIlluminate/Foundation/Support/Providers/RouteServiceProvider
を継承していて、$this->bootstrap()
で継承元のboot()
が呼ばれます。
public function boot()
{
//...
$this->loadRoutes();
$this->app->booted(function () {
$this->app['router']->getRoutes()->refreshNameLookups();
$this->app['router']->getRoutes()->refreshActionLookups();
});
}
$this->loadRoutes()
でapp/Providers/RouteServiceProvider::map()
をコールします。
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
これらのメソッドでroutes/web.php
とroutes/api.php
を読み込んでいます。
(ルート定義を別ファイルに書きたい場合はここに追加すればできます)
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
RouteファサードはIlluminate/Routing/Router
で解決するので、Illuminate/Routing/Router::middleware()
が呼ばれます。
しかし、Illuminate/Routing/Router::middleware()
メソッドはないので、__call()
が呼ばれます。
public function __call($method, $parameters)
{
//...
if ($method === 'middleware') {
return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
}
//...
}
Illuminate/Routing/RouteRegistrar::attribute('middleware', 'web')
として実行されます。
public function attribute($key, $value)
{
//...
$this->attributes[Arr::get($this->aliases, $key, $key)] = $value;
return $this;
}
$this->attributes['middleware'] = 'web'
のように登録されます。
namespace()
もほぼ同様なので割愛します。
->group(base_path('routes/web.php'))
を見ていきます。
Illuminate/Routing/RouteRegistrar::group()
が呼ばれています。
public function group($callback)
{
$this->router->group($this->attributes, $callback);
}
先程登録した$this->attributes
とパスをIlluminate/Routing/Router::group()
に委譲しています。
public function group(array $attributes, $routes)
{
$this->updateGroupStack($attributes);
$this->loadRoutes($routes);
array_pop($this->groupStack);
}
$this->updateGroupStack()
で$this->groupStack
というプロパティに$attributes
を格納しています。
プロパティの中身
いよいよ$this->loadRoutes()
でルートを読み込んでいきます。
protected function loadRoutes($routes)
{
(new RouteFileRegistrar($this))->register($routes);
}
public function register($routes)
{
$router = $this->router;
require $routes;
}
requireでbase_path('routes/web.php')
を読み込んでいます。
ルート定義では基本的にRouteファサードを使っていると思いますが、$router
変数にIlluminate/Routing/Router
を格納しているので$router->get()
のように書くこともできるようです。
routes/web.php
の中身がどのように読み込まれていくか下記のroutes/web.php
を例に見ていきます。
Route::get('/', [HogeController::class, 'index']);
Route::post('/', [HogeController::class, 'create']);
まずはget()
から見ていきます。
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
public function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri, $action));
}
$this->createRoute()
がなにを返すのかを見ていきましょう。
protected function createRoute($methods, $uri, $action)
{
//...
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);;
return $route;
}
protected function newRoute($methods, $uri, $action)
{
return (new Route($methods, $uri, $action))
->setRouter($this)
->setContainer($this->container);
}
$this->createRoute()
は最終的にIlluminate/Routing/Route
を返すようです。
コンストラクタは、
public function __construct($methods, $uri, $action)
{
$this->uri = $uri;
$this->methods = (array) $methods;
$this->action = $this->parseAction($action);
if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) {
$this->methods[] = 'HEAD';
}
if (isset($this->action['prefix'])) {
$this->prefix($this->action['prefix']);
}
}
Route::get('/', [HogeController::class, 'index'])
の例だと、
$this->uri = '/'
(prefixがあればprefix付き)
$this->method = ['GET', 'HEAD']
$this->action = ['uses' => 'HogeController@method', 'controller' => 'HogeController@method']
ルーティングの書き方
生成されたIlluminate/Routing/Route
クラスは$this->routes->add()
でIlluminate/Routing/RouteCollection
に配列として保存されます。
このようにしてルートファイルに定義されたルートが保存されていきます。
Route::post()
はmethod
が変わるだけでRoute::get()
と変わらないので割愛します。
これでやっと$this->loadRoutes()
が終わりapp/Providers/RouteServiceProvider::map()
内の->group()
に戻ってこれます。
public function group(array $attributes, $routes)
{
$this->updateGroupStack($attributes);
$this->loadRoutes($routes);
array_pop($this->groupStack);
}
最後に用済みとなった$this->groupStack
は消されて終わります。
このサイクルをデフォルトではroutes/web.php
とroutes/api.php
の2回行います。
参考
####クラスの関係
Illuminate/Routing/Router
-> Routeファサードの実態。
Illuminate/Routing/Route
-> 1つ1つのルートのuri, action, methodなどを保持。
Illuminate/Routing/RouteCollection
-> Routeクラスを配列としてまとめて保持する。
Illuminate/Routing/RouteRegistrar.php
-> map()
で呼ばれる。prefix, nameなどを登録していく。
プロパティの中身
Array
(
[0] => Array
(
[middleware] => Array
(
[0] => web
[1] => auth:admin
)
[prefix] => admin
[as] => admin.
[namespace] => App\Http\Controllers
)
[1] => Array
(
[middleware] => Array
(
[0] => web
[1] => auth:admin
)
[prefix] => admin/book
[as] => admin.book.
[namespace] => App\Http\Controllers
[where] => Array
(
)
)
)
Array
(
[0] => Array
(
[middleware] => Array
(
[0] => web
[1] => auth:user
)
[prefix] => user
[as] => user.
[namespace] => App\Http\Controllers
)
[1] => Array
(
[middleware] => Array
(
[0] => web
[1] => auth:user
)
[prefix] => user/book
[as] => user.book.
[namespace] => App\Http\Controllers
[where] => Array
(
)
)
)
ルーティングの書き方
Route::get('/', 'HogeController@method')
Route::get('/', ['uses' => 'HogeController@method'])
Route::get('/', [ HogeController::class => 'method'])
最終的に以下の形にパースされます。
[
'uses' => 'HogeController@method',
'controller' => 'HogeController@method',
]