PHP
laravel
コードリーディング
laravel5
新人プログラマ応援

public/index.phpからミドルウェア/コントローラーが実行されるまで - Laravelコードリーディング

テーマ & 前置き

Laravel5.6のコードを読んでいきます。

一般的なWebアプリケーションフレームワークではリクエストがあると/public/index.phpが実行されるようになってます。apacheなら.htaccessの設定とかnginxのconfファイルの設定です。

ただし、我々が普段Webアプリケーションの開発をする場合、それはModel/View/Controllerを開発することとほぼ同意かとおもいます。(俺はRepositoryもつくってるとか、MVCとは限らないとかはとりあえず置いておいて。)public/index.phpを意識することなんてほぼないでしょう。

そこで、public/index.phpからはじまって我々が普段開発しているControllerの処理が実行されるまでの経緯を順に追ってみたいと思います。超めんどい(本音)

ちょっと雑な記述も多いかもしれませんが、メモ程度でも残しておけば、これから読む人の手助けになるかもしれないにゃ?と思ったので記事にしました。

Let's コードリーディング!!

重要そうなところだけ抜粋していきます。
メソッドの途中も特に不要と判断したらなんの断りもなく削除して抜粋します。

最初はpublic/index.php

public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';

bootstrap/app.phpでは、Applicationのインスタンスを作っている。

bootstrap/app.php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

return $app;

LaravelではApplicationクラスはDIコンテナ(=サービスコンテナ)です。
ここでHttp\KernelとConsole\Kernelをコンテナに登録している。
public/index.phpの続きが以下。

public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

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

Kernelのインスタンスを作って使っている。
ここではConsoleではなくてHttpの方、つまり、App\Http\Kernelの方を追ってみる。

Kernelクラス

handleメソッドは親クラスのApp\Http\KernelはIlluminate\Foundation\Http\Kernelにある。

vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function handle($request)
{  
    $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());
}

$this->app->instance('request', $request)のinstanceメソッドはIlluminate\Container\Containerクラスに定義されている。
既にあるインスタンスをコンテナに登録する場合に利用するメソッドで、Requestをコンテナに登録したということ。

その下ですぐにインスタンスをclearしてない?と思うかもしれないが、Facade自体が持っているインスタンスをコンテナのインスタンスに置き換えるために必要な処理。一応Facadeクラスの該当メソッドと関連をみてみる。

vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
abstract class Facade
{
    protected static $resolvedInstance;

    public static function clearResolvedInstance($name)
    {   
        unset(static::$resolvedInstance[$name]);
    } 

    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        return static::$resolvedInstance[$name] = static::$app[$name];
    }
}

resolveFacadeInstanceメソッドで、$resolvedInstanceがない場合はコンテナからインスタンスを取得しているのがわかる。

Illuminate\Foundation\Http\Kernelに戻る。
$this->bootstrap();では、アプリケーションに必要な基底クラス群をインスタンス化してコンテナに登録していっている。

vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected $bootstrappers = [ 
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    \Illuminate\Foundation\Bootstrap\BootProviders::class,
];  

public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

protected function bootstrappers()
{
    return $this->bootstrappers;
}

ApplicationクラスのbootstrapWithメソッドに$bootstrappersを渡している。
bootstrapWithメソッドをみてみる。

vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function bootstrapWith(array $bootstrappers)
{    
    $this->hasBeenBootstrapped = true;

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

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

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

$bootstrappersの各クラスをインスタンス化して、bootstrapメソッドを実行している。
$bootstrappersに定義されるクラスはすべてbootstrapメソッドを持っており、その中で様々なサービスをインスタンス化し、コンテナに登録している。また、bootstraの名前が示す通り、アプリケーションの基本的な設定なども同時に行なっている。

Illuminate\Foundation\Bootstrap\LoadConfigurationを追ってみる。

vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php
public function bootstrap(Application $app)
{
     $items = [];

     if (file_exists($cached = $app->getCachedConfigPath())) {
         $items = require $cached;

         $loadedFromCache = true;
     }   

     $app->instance('config', $config = new Repository($items));

     if (! isset($loadedFromCache)) {
         $this->loadConfigurationFiles($app, $config);
     }       

     $app->detectEnvironment(function () use ($config) {
         return $config->get('app.env', 'production');
     });

     date_default_timezone_set($config->get('app.timezone', 'UTC'));

     mb_internal_encoding('UTF-8');
}

configファイルを読み込んでコンテナに登録した後、アプリの実行環境を設定したり、タイムゾーンの設定を設定したり、内部エンコーディングをUTF-8に設定したりしている。

目的地到着

Illuminate\Foundation\Http\Kernelに戻る。最後の部分。

vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected function sendRequestThroughRouter($request)
{

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

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

        return $this->router->dispatch($request);
    };  
} 

$this->routerはIlluminate\Routing\Routerオブジェクト。dispatchを確認して見る。

vendor/laravel/framework/src/Illuminate/Routing/Router.php
public function dispatch(Request $request)
{    
    $this->currentRequest = $request;

    return $this->dispatchToRoute($request);
}

public function dispatchToRoute(Request $request)
{    
    return $this->runRoute($request, $this->findRoute($request));
}

protected function runRoute(Request $request, Route $route)
{
    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}

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()
                        );
                    });
}

findRouteは文字通り、リクエストから対応するルーティングを見つけている。
prepareResponse はControllerのactionメソッドの戻り値をResponseオブジェクトに変換するもの。

そして、runRouteWithinStackが今回の目的地。
ここで登録してあるミドルウェアの処理とアクセスされたルーティングの処理(=Controllerのactionメソッド)が実行される。

Pipeline周り

Pipelineの動きを紐解きつつ具体的に見るのは後日、別記事にてたぶん書く(予定は未定)

最後に

間違えあったら修正おなしゃーす。