0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel 7.4 Kernelを読む

Posted at

Laravel 7.4 Kernelのコードを読む。

bind

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内部で行われる。

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に変換する。

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に戻ってくる。

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をレスポンスする。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?