Laravel 7.4 Kernelを読む

Laravel 7.4 Kernelのコードを読む。

## bind

```lang:bootstrap/app.php
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
```

Kernelは `bootstrap/app.php` でサービスコンテナにシングルトンとしてbindされる。
abstractは `Illuminate\Contracts\Http\Kernel` だ。

## App\Http\Kernel.php

```
namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
```
`Illuminate\Foundation\Http\Kernel.php`を継承している。ロジックはここに書かれてる。
App\Http\Kernel.phpは App\Httpにあるので実装者が挙動を変えることができる。

デフォルトでは
- $middleware
- $middlewareGroups
- $routeMiddleware
のプロパティが用意されている。

## Illuminate\Contracts\Http\Kernel.php
まずはインターフェースを追う。結構シンプル。

```
interface Kernel
{
    // 起動処理
    public function bootstrap();
    // requestをresponseに変換
    public function handle($request);
    // 終了処理
    public function terminate($request, $response);
    // LaravelApplicationインスタンスを返す
    public function getApplication();
}
```


## Illuminate\Foundation\Http\Kernel.php
Laravelが用意したHttpカーネルの実態。
`Illuminate\Contracts\Http\Kernel.php` を継承している。

```
    public function __construct(Application $app, Router $router)
    {
        $this->app = $app;
        $this->router = $router;

        $this->syncMiddlewareToRouter();
    }
```
まずはコンストラクタ。
引数でApplicationとRouterを受け取ってプロパティに代入する。Kernelクラスはサービスコンテナでインスタンス化されるので引数はDI代入される。
引数に型指定してある。Applicationクラスのコンストラクタは型指定していないのになんでだろう。

```
   protected function syncMiddlewareToRouter()
    {
        $this->router->middlewarePriority = $this->middlewarePriority;

        foreach ($this->middlewareGroups as $key => $middleware) {
            $this->router->middlewareGroup($key, $middleware);
        }

        foreach ($this->routeMiddleware as $key => $middleware) {
            $this->router->aliasMiddleware($key, $middleware);
        }
    }
```
MiddlewareをRouteに渡してる。なぜKernelでやるのだろう・・・？(ConfigからMiddlewareを読み込むこともできると思うけど重要度的にKernelでやった方が良いのかな)

## Kernelライフサイクル
Kernelのライフサイクルを追う。

## handle関数
`bootstrap/app.php`でKernelがBindされた後、`public/index.php`でhandle関数が実行される。
handle関数ではrequestからresponseへの変換を行う。

```
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
```

```
    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Throwable $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new RequestHandled($request, $response)
        );

        return $response;
    }
```
結構シンプル。

```
$request->enableHttpMethodParameterOverride();
```
最初にHttpMethodParameterOverrideを有効化してる。これはLaravelではなくLaravelの元?になったSymfonyの機能。
HttpMethodはGETやPOSTに加えPUTやDELETE等がある。しかしHtmlのFormではGETとPOSTしか使えない(ブラウザが未対応)。擬似的に他Methodを使用する方法としてhidden属性で_methodパラメータにMethodを書いて送信する方法がある。ここではこの機能を有効化している。enableHttpMethodParameterOverride関数のコメントにはこれによりCSRFの脆弱性を引き起こすので注意するよう書かれている。

```
$response = $this->sendRequestThroughRouter($request);

    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);

        Facade::clearResolvedInstance('request');

        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

```
MiddlewareやRouterを通してresponse変換を行なっている。
1. まずサービスコンテナにrequestの登録を行い、
2. ファサードの'request'内部処理キャッシュを削除し、
3. bootstrapを実行
4. Middlewearとrouterに処理を渡してresponseをreturn;


```
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
```
bootstrapではApplicationのbootstrapWith関数にkernelが指定したbootstrapクラスを渡している。
bootstrap処理はApplication内部で行われる。

```lang:Application.php
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
        }
    }
```

Application.phpのbootstrapWithでは引数のbootstrapクラスを`$this->make`でインスタンス化しbootstrap関数を実行している。合わせてbootstrapイベントを実行している。

```
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
```
Pipelineではmiddlewareを実行し、最後にdispatchToRouter関数を実行している。

```
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }
```
dispatchToRouter関数(が作成するfunction)では$requestを受け取りrouterを起動している。
dispatch関数では内部でdispatchToRoute関数を実行。


```
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
```
findRouteで$requestにマッチするrouteを見つけ、runRouteを実行する。
RouterとRouteがごっちゃにならないよう注意したい。

```
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        $this->events->dispatch(new RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $request)
        );
    }
```
`$this->runRouteWithinStack($route, $request)`でRouteMiddlewareに実行やControllerの起動を行う。

```
    protected function runRouteWithinStack(Route $route, Request $request)
    {
        $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                                $this->container->make('middleware.disable') === true;

        $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

        return (new Pipeline($this->container))
                        ->send($request)
                        ->through($middleware)
                        ->then(function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run()
                            );
                        });
    }
```
ここでもPipelineが出てくる。最終的には`$route->run()`でresponse(で使える値)を作成し、`$this->prepareResponse`でHttpFoundation\Responseに変換する。

```
    public function run()
    {
        $this->container = $this->container ?: new Container;

        try {
            if ($this->isControllerAction()) {
                return $this->runController();
            }

            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }

    protected function runController()
    {
        return $this->controllerDispatcher()->dispatch(
            $this, $this->getController(), $this->getControllerMethod()
        );
    }

```
コントローラーなら`$this->runController()`を実行する。

```
    public function dispatch(Route $route, $controller, $method)
    {
        $parameters = $this->resolveClassMethodDependencies(
            $route->parametersWithoutNulls(), $controller, $method
        );

        if (method_exists($controller, 'callAction')) {
            return $controller->callAction($method, $parameters);
        }

        return $controller->{$method}(...array_values($parameters));
    }
```
そうして遂にコントローラーの関数を実行する。
callAction関数が定義されているとそっち優先になるのか。
コントローラー関数の戻り値は大抵viewクラスになる。viewのままだとresponseとして使えないので呼び出し元でresponseに変換する。

```lang:Router.php
    public static function toResponse($request, $response)
    {
        if ($response instanceof Responsable) {
            $response = $response->toResponse($request);
        }

        if ($response instanceof PsrResponseInterface) {
            $response = (new HttpFoundationFactory)->createResponse($response);
        } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
            $response = new JsonResponse($response, 201);
        } elseif (! $response instanceof SymfonyResponse &&
                   ($response instanceof Arrayable ||
                    $response instanceof Jsonable ||
                    $response instanceof ArrayObject ||
                    $response instanceof JsonSerializable ||
                    is_array($response))) {
            $response = new JsonResponse($response);
        } elseif (! $response instanceof SymfonyResponse) {
            $response = new Response($response, 200, ['Content-Type' => 'text/html']);
        }

        if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
            $response->setNotModified();
        }

        return $response->prepare($request);
```
型に合わせてresponse変換を行う。


そうして最終的に `index.php`に戻ってくる。

```lang:index.php
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();
```

`$response->send();`でHeaderへの書き込みやcontentをechoする。

```
    public function send()
    {
        $this->sendHeaders();
        $this->sendContent();

        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }

        return $this;
    }

    public function sendContent()
    {
        echo $this->content;

        return $this;
    }
```

後はphp-fpmが終了通知を受け取ってhttpをレスポンスする。