LoginSignup
184
131

More than 3 years have passed since last update.

魔法のようなLaravelも素のPHPに始まり素のPHPに終わる。Laravelのライフサイクルを簡単に解説。

Last updated at Posted at 2020-03-09

はじめに

Untitled Diagram.png

普段Laravelでアプリケーションを実装するときは
コントローラでリクエストを受け取り、
コントローラでレスポンスを返却するまでの範囲しか
あまり意識していませんでした。

ですが、実際には
WEBサーバがHTTPリクエストを受け取ってから
コントローラに処理が渡されるまでの間や、
コントローラからレスポンスを返却してから
WEBサーバがHTTPレスポンスを送信するまでの間
にはいろいろと処理が行われているはずです。

/publicディレクトリに実際のHTMLファイルはないけどなぜルーティング処理されるんだ?
・どこの誰がどうやって処理対象のコントローラを特定してくれているんだ?
・あの便利なRequestインスタンスはいつ誰が作って渡してくれているんだ?
などなど
普段意識していなかった
コントローラより外の世界 が気になり、
Laravelのコードを追いかけてみましたので解説したいと思います。

今回はLaravelのソースコードの細かい分岐処理などは全て省略し、
メインフローのみピックアップして解説していくので
難しい複雑なコードはあまり登場しません。

Laravel6.2を元に解説を進めますが、
ほかのバージョンでも大した差はないと思います。

全体の流れ

全体の流れとしてはこのようになっています。

  • WEBサーバがindex.phpにリダイレクト
  • index.phpの中
    • autoload読み込み
    • Applicationインスタンス作成
    • HttpKernelインスタンス作成
    • Requestインスタンス作成
    • HttpKernelがリクエストを処理してResponse取得
    • レスポンス送信
    • terminate()で後片付け

この1つ1つをもう少し詳しく解説していきます。

WEBサーバがindex.phpにリダイレクト

Laravelのドキュメントルートは/public/です。

例えばhttps://example.com/test/のURLにリクエストされたとして
通常であれば
/public/test/index.htmlを取得することになります。

しかし、Laravelではすべての処理の始まりが/public/index.phpのファイルになるので、
WEBサーバの設定で
どのURLに対するリクエストでも、
全て/public/index.phpにリダイレクトするようになっています。1
 
WEBサーバがApacheの場合は.htaccessファイルに
この様にリダイレクト処理を書きます。

.htaccess
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

(この.htaccess設定はデフォルトで/public/.htaccessに配置されています)

WEBサーバがNginxの場合はconfファイルに
この様にリダイレクト処理を書きます。

default.conf
location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

これらのWEBサーバ設定によって、
まずは全ての入り口となる/public/index.phpに入っていきます。

index.phpの中

実際のファイルがこちら。

/public/index.php
require __DIR__.'/../vendor/autoload.php'; // 【1】autoload読み込み

$app = require_once __DIR__.'/../bootstrap/app.php'; // 【2】Applicationインスタンス作成

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 【3】HttpKernelインスタンス作成

$response = $kernel->handle( // 【5】HttpKernelがリクエストを処理してResponse取得
    $request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成
);

$response->send();  // 【6】レスポンス送信

$kernel->terminate($request, $response); // 【7】terminate()で後片付け

このindex.phpファイルが、Laravelの処理全体フローそのものになります。
【1】~【7】までをざっくりと追いかけていきます。

その中でも

【5】HttpKernelがリクエストを処理してResponse取得

は一番のメイン処理で、
僕らの知るコントローラにたどり着くまで
コードを深く深く見ていくことになります。

【1】autoload読み込み

まず初めに

/public/index.php
require __DIR__.'/../vendor/autoload.php'; // 【1】autoload読み込み

でautoloadファイルを読み込みます。

PHPでは別ファイルのクラスを読み込む際は
requireを使ってファイルを読み込む必要がありますが、
このautoloadを最初に読み込むことで
いちいちrequireでファイルを読み込むことなく別ファイルのクラスを
利用できるようになります。

autoloadの詳しい仕様については割愛します。
参考:https://laraweb.net/surrounding/1642/

【2】Applicationインスタンス作成

次に、

/public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php'; // 【2】Applicationインスタンス作成

ここでApplicationインスタンスを作成しています。
/bootstrap/app.phpの中も見てみます。

/bootstrap/app.php(重要箇所のみ抜粋)
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

return $app;

Illuminate\Foundation\Applicationクラスをインスタンス化して返却しています。
このApplicationインスタンスが、通称「サービスコンテナ」と呼ばれるものです。

このサービスコンテナについては
こちらの記事で解説しているので
興味のある方はご覧ください。
【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。

全体処理の流れをざっくり見る
という目的なので、今は気にせず次に進んでいいかと思います。

【3】HttpKernelインスタンス作成

次に、

/public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); // 【3】HttpKernelインスタンス作成

でHttpKernelクラスをインスタンス化しています。

この$app->make()メソッドは、
単純にKernelクラスをnewしているだけと思って大丈夫です。
※詳しく知りたい方はこちらの記事でサービスコンテナについて解説しています
【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。

kernelとは「核」とか「核心」という意味で、
ソフトウェアの世界では
「司令塔となる中心の機能」
というイメージで僕はとらえています。

今回のHttpKernelの場合は
「Http処理全体の司令塔となるクラス」というイメージ。

【4】Requestインスタンス作成

次に、

/public/index.php
$response = $kernel->handle( // 【5】HttpKernelがリクエストを処理してResponse取得
    $request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成
);

この$kernel->handle()の引数として渡されている

/public/index.php
$request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成

ここの処理でRequestインスタンスを作成しています。

このcapture()の中を見てみます。

/vendor/laravel/framework/src/Illuminate/Http/Request.php(重要箇所のみ抜粋)
public static function capture()
{
    return static::createFromBase(SymfonyRequest::createFromGlobals());
}

さらにSymfonyRequest::createFromGlobals()の中を見てみます。
※ここから先はSymfonyのコードになります2
 

/vendor/symfony/http-foundation/Request.php(重要箇所のみ抜粋)
public static function createFromGlobals()
{
    $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

    return $request;
}

ここで、PHPのスーパーグローバル変数である
$_GET$_POST$_COOKIE$_SERVER
などを使っていますね。

素のPHPの機能で、
GET・POSTリクエスト内容や受け取ったCookie、リクエストのURLやHTTPメソッドなど
様々なHTTPリクエストの情報を取得することができます。
https://www.php.net/manual/ja/reserved.variables.server.php

 
 
さらに、
そのPHPのスーパーグローバル変数で取得した
リクエスト情報を渡しているself::createRequestFromFactoryの中を見てみます。

/vendor/symfony/http-foundation/Request.php(重要箇所のみ抜粋)
    private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self
    {
        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
    }

受け取った様々なリクエスト情報を引数に渡して、
ここでやっとRequestクラスをインスタンス化しました。
(この時点ではLaravelのRequestクラスではなくSymfonyのRequestクラスです)

 

そして、そのSymfonyRequestインスタンスを返却しています。

このSymfonyのRequestインスタンスが返却される途中、
先ほど見たこの処理のstatic::createFromBase
LaravelのRequestクラスインスタンスに変換されています。

/vendor/laravel/framework/src/Illuminate/Http/Request.php(重要箇所のみ抜粋)
public static function capture()
{
    return static::createFromBase(SymfonyRequest::createFromGlobals());
}

 
 
これでようやく
この$requestにRequestクラスのインスタンスを入れるところまで完了しました。

/public/index.php
$response = $kernel->handle( // 【5】HttpKernelがリクエストを処理してResponse取得
    $request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成
);

【5】HttpKernelがリクエストを処理してResponse取得

次は

/public/index.php
$response = $kernel->handle( // 【5】HttpKernelがリクエストを処理してResponse取得
    $request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成
);

ここでHttpKernelhandle()メソッドにRequestを渡して、
Responseを受け取っています。

このhandle()の中を見てみます。

/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(重要箇所のみ抜粋)
public function handle($request)
{
    $response = $this->sendRequestThroughRouter($request);

    return $response;
}

さらに$this->sendRequestThroughRouter()の中を見てみます。

/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(重要箇所のみ抜粋)
    protected function sendRequestThroughRouter($request)
    {
        $this->bootstrap();

        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->middleware)
                    ->then($this->dispatchToRouter());
    }

$this->bootstrap();
では実際のリクエストの処理の前に、もろもろ設定の読み込みなどを行っています。
具体的には、.envの環境変数の読み込みや、
各サービスプロバイダの実行などです。
(メイン処理の流れとは外れるため詳細は割愛)

こちらがメインのリクエスト処理フローです。

return (new Pipeline($this->app))
            ->send($request)
            ->through($this->middleware)
            ->then($this->dispatchToRouter());

このPipelineクラスの仕様解説は割愛しますが、
見たまんま、
->send($request)(Requestインスタンスを渡して)
->through($this->middleware)(ミドルウェアを通して)
->then($this->dispatchToRouter());(ルーターの処理を実行)
という処理です。

この

->through($this->middleware)(ミドルウェアを通して)

ではグローバルミドルウェアの処理が実行されます。

そしてメイン処理の続きは
->then($this->dispatchToRouter());(ルーターの処理を実行)
ですね。

このdispatchToRouter()の先では、
・対象のルーティングを取得する
・そのルーティングに紐づいたコントローラとコントローラアクションメソッドを判定する
・そのコントローラアクションメソッドを実行する
・コントローラから返されたレスポンスをResponseクラスのインスタンスに変換する
・Responseインスタンスを返却する
という感じで処理が進みます。

この先はさらにコードの深いところに入っていくので、
現時点で疲れていれば上記の概要だけ把握して
次の「【6】レスポンス送信」まで飛ばしちゃってもいいと思います。
 
 
ではdispatchToRouter()の先を見ていきます。

/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(重要箇所のみ抜粋)
protected function dispatchToRouter()
{
    return $this->router->dispatch($request);
}

HttpKernelクラスから、Routerクラスに処理が渡されました。

さらにdispatch()を見ていきます。

/vendor/laravel/framework/src/Illuminate/Routing/Router.php(重要箇所のみ抜粋)
public function dispatch(Request $request)
{
    return $this->dispatchToRoute($request);
}

さらにdispatchToRoute()の中。

/vendor/laravel/framework/src/Illuminate/Routing/Router.php(重要箇所のみ抜粋)
public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

$this->findRoute($request)
で、/routes/web.phpに定義したルーティングリストの中から
今回のリクエストに合致するものを取得してきます。

そしてその特定したルートを渡して
runRoute()で処理を続行。

/vendor/laravel/framework/src/Illuminate/Routing/Router.php(重要箇所のみ抜粋)
protected function runRoute(Request $request, Route $route)
{
    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}

さらにrunRouteWithinStack()の中。

/vendor/laravel/framework/src/Illuminate/Routing/Router.php(重要箇所のみ抜粋)
protected function runRouteWithinStack(Route $route, Request $request)
{
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then($route->run());
}

先ほどの見覚えのある処理が出てきました。

特定したルート($route)をもとに、
->send($request)(Requestインスタンスを渡して)
->through($middleware)(ミドルウェアを通して)
->then($route->run())(ルートの処理を実行)
となっています。

先ほどはグローバルミドルウェアの処理が実行されていましたが、
今回の
->through($middleware)では
ルートミドルウェアを実行しています。

そして、
$route->run()でRouterクラスからさらにRouteクラスに処理が渡されました。

$route->run()の中。

/vendor/laravel/framework/src/Illuminate/Routing/Route.php(重要箇所のみ抜粋)
public function run()
{
    return $this->runController();
}

やっとコントローラにたどり着けそうな感じがしてきました。

runController()の中。

/vendor/laravel/framework/src/Illuminate/Routing/Route.php(重要箇所のみ抜粋)
protected function runController()
{
    return $this->controllerDispatcher()->dispatch(
        $this, $this->getController(), $this->getControllerMethod()
    );
}

$this->getController()では、
/routes/web.phpに書かれていた
{コントローラクラス名}@{アクションメソッド名}
の文字列を取得して@で文字列分割し、
{コントローラクラス名}でコントローラをインスタンス化しています。
(「UserController@index」とかっていつも書くやつですね)

/vendor/laravel/framework/src/Illuminate/Routing/Route.php(重要箇所のみ抜粋)
public function getController()
{
    $class = $this->parseControllerCallback()[0]; // 「@」で分割してコントローラクラス名を取得
    $this->controller = $this->container->make(ltrim($class, '\\')); // サービスコンテナのmake()でインスタンス化

    return $this->controller; // コントローラインスタンスを返却
}

「UserController@index」の例では、UserControllerクラスがインスタンス化されることになります。3
  
 
$this->getControllerMethod()の方では、
同じように
{コントローラクラス名}@{アクションメソッド名}を「@」で分割して
{アクションメソッド名}を文字列で返却しています。
「UserController@index」の例では、「index」の文字列が取得されることになります。
 
 
そして改めてrunController()の中を見ると、

/vendor/laravel/framework/src/Illuminate/Routing/Route.php(重要箇所のみ抜粋)
protected function runController()
{
    return $this->controllerDispatcher()->dispatch(
        $this, $this->getController(), $this->getControllerMethod()
    );
}

$this->controllerDispatcher()->dispatch()
先ほどとってきたコントローラクラスアクションメソッド名を渡しています。

dispatch()の中。

/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(重要箇所のみ抜粋)
public function dispatch(Route $route, $controller, $method)
{
    return $controller->{$method}(...array_values($parameters));
}

ここでついに・・・。
コントローラのアクションメソッドが実行されます。

「UserController@index」の例では、
$controllerにはUserControllerのインスタンスが、
$methodには「index」の文字列が入っていますので、
$controller->index()の形でちゃんとメソッドが実行されていますね。

長かったですが、
WEBサーバでリダイレクトされて/public/index.phpに入ったところから、
僕らがいつも意識しているコントローラの中の処理に入るまでに
この様な処理をたどっているわけです。

コントローラから返却されたレスポンスは
今まで来た道を帰っていき、
だいぶ前にみた

/vendor/laravel/framework/src/Illuminate/Routing/Router.php(重要箇所のみ抜粋)
protected function runRoute(Request $request, Route $route)
{
    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}

このprepareResponse()の中で、
LaravelのResponseインスタンスに変換されます。

そして最終的に最初の/public/index.phpにある

/public/index.php
$response = $kernel->handle( // 【5】HttpKernelがリクエストを処理してResponse取得
    $request = Illuminate\Http\Request::capture() // 【4】Requestインスタンス作成
);

ここまで帰って$responseに格納されます。

【6】レスポンス送信

次に、

/public/index.php
$response->send();  // 【6】レスポンス送信

ここで実際にレスポンスを送信します。

$response->send();の中を見てみます。

/vendor/symfony/http-foundation/Response.php(重要箇所のみ抜粋)
public function send()
{
    $this->sendHeaders();
    $this->sendContent();
}

見た通り、
sendHeaders()ではHttpレスポンスヘッダーを送信し、
sendContent()ではレスポンス内容(HTMLなど)を送信しています。

sendHeaders()の中。

/vendor/symfony/http-foundation/Response.php(重要箇所のみ抜粋)
public function sendHeaders()
{
    // headers
    foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
        $replace = 0 === strcasecmp($name, 'Content-Type');
        foreach ($values as $value) {
            header($name.': '.$value, $replace, $this->statusCode);
        }
    }

    // cookies
    foreach ($this->headers->getCookies() as $cookie) {
        header('Set-Cookie: '.$cookie, false, $this->statusCode);
    }

    // status
    header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
}

いろいろと書いてありますが、
header()とある3か所だけ見てみるといいです。

素のPHPのheader()メソッドを使って、
Content-TypeCookieHTTPステータスなどの
HTTPヘッダー情報を送信していますね。

続いてsendContent()の中。

/vendor/symfony/http-foundation/Response.php(重要箇所のみ抜粋)
public function sendContent()
{
    echo $this->content;
}

contentをechoしています。
このcontentには表示する画面のHTMLがそのまま入っています。

これで実際にHttpレスポンスを送信完了しました。

【7】terminate()で後片付け

/public/index.phpの最後の処理です。

/public/index.php
$kernel->terminate($request, $response); // 【7】terminate()で後片付け

ここでは、各ミドルウェアクラスに定義されたterminate()処理などを実行しているだけです。

これで、ついにLaravelの処理がすべて終了しました!

おわりに

普段意識しない、
コントローラより前の世界、後の世界を見ていきました。

個人的には、
・まず最初に素のPHPの$_GET$_SEVERなどを使ってRequestクラスを作っている
・最後は素のPHPのheader()echo()でレスポンスを送信している
というところを実際に見れたのが面白かったです。

すごく便利で魔法のようなフレームワークの機能たちに囲まれて開発していますが、
やっぱり一番最初と最後は僕らの知っている素のPHPの機能を使って処理をしているんだなと・・・。
 
 
今回は実際のLaravelソースコードから
重要処理のみを抜粋してできる限り読み進めやすいようにしています。

実際にはもっとたくさん分岐処理などがありますので、
自分で実物のコードを追いかけてみることをお勧めします。

Laravelのちょっと深いところを理解していきたいと思っている方、
これらの記事もおすすめですので是非読んでみてください。
(次に進む前に、LGTMしてもらえるとうれしいです)
魔法のようなLaravelも素のPHPに始まり素のPHPに終わる。Laravelのライフサイクルを簡単に解説。
【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。
【Laravel】引数でタイプヒントしただけでインスタンスがもらえるのはなぜ?Laravelの魔法を解明してみる。
【Laravel】ファサードとは?何が便利か?どういう仕組みか?

参考

【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。
【Laravel】引数でタイプヒントしただけでインスタンスがもらえるのはなぜ?Laravelの魔法を解明してみる。
https://readouble.com/laravel/6.x/ja/lifecycle.html
https://qiita.com/yamaji_daisuke/items/e661ffd63eefdfd178cc
https://speakerdeck.com/namizatork/laravelwakanne-kara-wan-quan-nili-jie-sita-madesutetupuatupu


  1. 実際は、CSSやJSなどpublicディレクトリ配下に実際に存在するファイルの場合はリダイレクトされない 

  2. Laravelは、Symfonyという別のPHPフレームワークをベースに作られており、コードを深くたどるとSymfonyになっています  

  3. このmake()で依存解決が行われます。依存解決とは、コンストラクタの引数で型宣言されているクラスをインスタンス化して渡してくれること(詳細はこちらの記事で解説) 

184
131
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
184
131