Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
32
Help us understand the problem. What is going on with this article?
@yamaji_daisuke

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

More than 1 year has passed since last update.

テーマ & 前置き

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の動きを紐解きつつ具体的に見るのは後日、別記事にてたぶん書く(予定は未定)

最後に

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

32
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
yamaji_daisuke
ニートエンジニア。去年くらいはもっぱらPHP/Laravelでサーバーサイドやってました。最近はPython3でAIのお勉強している文系デベロッパーです。上流から下流まで全部やるのが好き。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
32
Help us understand the problem. What is going on with this article?