laravelでweb.phpが読み込まれるまでの道のり

## はじめに

laravelではルーティングの定義を`routes/web.php`などに記載しますが、どのようにそれが読み込まれてどこに情報が保持されるのかをソースコードを読んで追っていきたいと思います。


## Routeファサード

Routeファサードで解決されるクラス`Illuminate/Routing/Router.php`



```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()`が呼ばれます。

```php
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()`をコールします。

```php
public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();
}
```

これらのメソッドで`routes/web.php`と`routes/api.php`を読み込んでいます。
（ルート定義を別ファイルに書きたい場合はここに追加すればできます）

```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()`が呼ばれます。

```php
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')`として実行されます。

```php
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()`が呼ばれています。

```php
public function group($callback)
{
    $this->router->group($this->attributes, $callback);
}
```

先程登録した`$this->attributes`とパスを`Illuminate/Routing/Router::group()`に委譲しています。

```php
public function group(array $attributes, $routes)
{
    $this->updateGroupStack($attributes);

    $this->loadRoutes($routes);

    array_pop($this->groupStack);
}
```

`$this->updateGroupStack()`で`$this->groupStack`というプロパティに`$attributes`を格納しています。
[プロパティの中身](#プロパティの中身)

いよいよ`$this->loadRoutes()`でルートを読み込んでいきます。

```php
protected function loadRoutes($routes)
{
    (new RouteFileRegistrar($this))->register($routes);
    
}
```

```php
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`を例に見ていきます。

```php
Route::get('/', [HogeController::class, 'index']);
Route::post('/', [HogeController::class, 'create']);
```

まずは`get()`から見ていきます。

```php
public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
```

```php
public function addRoute($methods, $uri, $action)
{
    return $this->routes->add($this->createRoute($methods, $uri, $action));
}
```

`$this->createRoute()`がなにを返すのかを見ていきましょう。

```php
protected function createRoute($methods, $uri, $action)
{
    
    //...

    $route = $this->newRoute(
        $methods, $this->prefix($uri), $action
    );;

    return $route;
}
```

```php
protected function newRoute($methods, $uri, $action)
{
    return (new Route($methods, $uri, $action))
                ->setRouter($this)
                ->setContainer($this->container);
}
```

`$this->createRoute()`は最終的に`Illuminate/Routing/Route`を返すようです。

コンストラクタは、

```php
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()`に戻ってこれます。

```php
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などを登録していく。

### プロパティの中身

```php
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'])`

最終的に以下の形にパースされます。

```php
[
    'uses' => 'HogeController@method',
    'controller' => 'HogeController@method',
]
```