1. HOME
  2. ブログ
  3. IT技術
  4. Laravelが文字列Hello Worldを画面に表示するまでの仕組みを理解する

Laravelが文字列Hello Worldを画面に表示するまでの仕組みを理解する

概要

Laravelに入門するために、リクエストを受け取ってからレスポンスを返すまでの仕組みをたどってみます。

ゴール

Hello Worldを題材にソースコードをざっくり読んでみることで、Laravelがどのようにリクエストをもとにレスポンスを組み立てているのか、概要を掴むことを目指します。
つまり、Laravelがどうやって動いているのか、ぼんやりとでもイメージできるようになることが目標です。

環境

  • PHP8.1
  • Laravel10.0

目次


用語

※ 用語の詳細は記事で掘り下げていきます。

  • リクエスト/レスポンス: HTTPリクエスト/HTTPレスポンスを指す
  • オブジェクト: クラスのインスタンスと同義
  • Service Container: いわゆるDIコンテナ Laravelではオブジェクトの作成・提供を責務に持つ
  • Service Provider: どういうオブジェクトをどうやってつくるのか機能単位にまとめたもの
  • Kernel: 核心などを意味する Laravelにおいてはアプリケーションを制御することを責務に持つ
  • Bootstrapper: 設定ファイルを読み出すなど、Laravelが動作するのに必要な準備を表す処理をまとめたもの

背景

Webアプリケーションフレームワークを理解する上で、リクエストからレスポンスを組み立てるまでの流れを知ることはとても重要です。
公式でも書かれている通り、仕組みを理解すれば自信をもってLaravelを利用したコードが書けるようになるはずです。

参考

仕組みを知り、自信をもつということは言い換えれば不具合や課題など、さまざまな種類の問題を解決する力を手にいれることを意味します。
Laravelともっと仲良くなるためにも、リクエストを扱う一連の流れを探っていきましょう。

ざっくり要約

public/index.phpが入り口。Laravelの核となるKernelが個々の処理を制御する。
まず、Laravelが動作するのに必要なオブジェクトをService Containerを介して生成。
具体的にどのオブジェクトをつくるのかは、Service Providerによって規定されている。
そして、リクエストを処理するのに必要なオブジェクトがそろったら、KernelオブジェクトがRouteオブジェクトを介して対応する処理を呼び出す。
出来上がったレスポンスを返却することで、処理が完了。


方針

Laravelがリクエストを処理するまでの道のりは、膨大な量のコードで構成されています。
すべてを読んでいると迷子になってしまうので、最初にやりたいことを決めておきます。

今回は見る範囲が広いので、2回に分けて全体の処理をたどっていきます。
具体的には、最初にLaravelがレスポンスを生成するために何をしているのか概要を見ていきます。先に全体像を押さえておくことで、詳細を見るときに何を目的とした処理なのか掴めるようになることを目指します。
続いて、具体的な仕組みを解き明かします。細部に入り込みすぎない程度に眺めていき、ざっくりどうやって実現しているのか主要なところを押さえていきます。

全体像・仕組みを押さえることができれば、Laravelがどうやって動いているのか多少なりとも見えてくるはずです。

範囲

Laravelのアプリケーションでは、データベースやBlade Templateなどを組み合わせることで、複雑な画面や機能も実装することができます。
とはいえ入門段階でこれらも含めた仕組みを解き明かそうと思うと、範囲が広すぎて途中で折れてしまいそうです。

よって、今回はいわゆるHello Worldに相当する処理がどのように実行されるのか、理解するところを目標とします。
より具体的には以下のような、レスポンスとして文字列を返却するようなコードを見ていきます。


それでは最初に全体像を知るために、リクエストからレスポンスをつくりだすときにLaravelが何をしているのか、簡単に見ていきます。

全体像を知る

文字列をレスポンスとして返すだけでも、Laravelではたくさんの処理を見ていくことになります。
ですので、いきなりコードから探り始めると、今見ている処理が何を目的としたものなのか見えてこず、理解が曖昧になってしまうかもしれません。

そこでまずは、Laravelがリクエストを扱うときに重要なオブジェクトの名前と役割をざっくり見ておきます。
どんな責務を持った処理が何をしているのか明らかにできれば、全体像も掴めてくるはずです。

必要な責務

細かな役割を見ていく前に、「リクエストからレスポンスを組み立てる」ためにどういう処理が必要か整理しておきましょう。
処理とは具体的に何か掘り下げておくことで、これから見ていくオブジェクトが処理のどの部分を担ってくれるのかイメージできるようにします。

具体的な流れは、以下の通りです。

  • リクエストをWebサーバから受け取る
  • リクエストを扱うのに必要なオブジェクトを用意
  • リクエスト先のURLをもとに呼び出す処理を振り分け
  • 該当する処理を呼び出し、リクエストをもとにレスポンスを生成
  • 出来上がったレスポンスをWebサーバへ返却

Webアプリケーションフレームワークを触ったことがあれば、なんとなくイメージできるかと思います。
Laravelがこのような処理をどうやって実現しているのか、関係する主なオブジェクトを見てみます。

Service Container-オブジェクトを用意して提供

参考

Laravelを構成するオブジェクトを用意・提供する処理は、Service Containerなるオブジェクトが担います。
これはいわゆるDIコンテナと呼ばれるものです。DIコンテナの役割やメリットはテストコードを題材とした方がわかりやすそうですが、今回は実装までを追うのが目標なので、割愛します。

先に概要を把握しておきたい方は、参考リンクの記事を読んでみてください。


実装の観点から見るService Containerの大きな役目は、リクエストを処理していくのに必要なオブジェクトを用意・提供することです。
これだけではイメージしづらいので、最初に見たHello Worldのコードを見てみましょう。

重要なのは、メソッドの引数で渡しているClosureです。この処理はHTTPリクエストを表わすRequestオブジェクトを受け取っています。
このように欲しいクラスを書いておくだけでインスタンスが得られるのは、Service Containerの恩恵によるものです。

※ ControllerのメソッドでRequestオブジェクトを受け取る場合も同様に考えられます。

少し話が抽象的になってきたので、ひとまずはService Containerがあることで、Laravelアプリケーションではオブジェクトを用意するのが楽になるんだな、ということを覚えておきましょう。

Service Provider-Laravelが動き出すための指示書

参考

Service Containerのおかげでオブジェクトを用意できる仕組みは整っているようです。
しかし、簡単にオブジェクトが用意できるようになっても、Laravelで扱うオブジェクトの数は膨大です。これを1つ1つ設定して準備するのは大変そうです。

そこで、Laravelではセッション管理やデータベースなど、機能を実現するためのオブジェクトをどうやって組み立てるのか表現したクラスが用意されています。
これは、機能(Service)を提供するための役割を持つことから、Service Providerと呼ばれます。
具体例として、コードが比較的シンプルなHashServiceProviderクラスを見てみましょう。このクラスは、パスワードのハッシュ化など、ハッシュ化処理に関係する機能を用意するのが役割です。

HashServiceProvider::register()の役目は、機能を実現するのに必要なオブジェクトを用意しておくことです。
オブジェクトをつくるとなるとスコープが気になるところですが、Service Containerがよしなに管理してくれます。
Service Containerが管理してくれていることで、必要に応じて必要な場所でオブジェクトを取り出せるようになります。

まとめると、Service Providerの責務はService Containerを介し、Laravelの各種機能を実現するためのオブジェクトを用意することです。
Service Containerと同様、普段の実装ではあまり意識することはありませんが、Laravelを支えるとても重要なオブジェクトです。

Kernel-Laravelの核

参考

Kernelは、Laravelのドキュメントではあまり触れられていない機能です。
ですが、Kernelという英単語が物事の核心といった意味を持つように、LaravelにおけるKernelクラスはとても重要な役割を担っています。

Kernelの責務は、Laravelアプリケーションを動かすことです。
より具体的には、これまで紹介してきたService ContainerやService Provider・そして私達が実装するアプリケーションと協力し、HTTPレスポンスなどの出力をつくりだします。
どのように協力していくのかは、ソースコードを見るときにじっくり探っていきます。
よってここでは、KernelはLaravelアプリケーションを制御する司令塔のような役目を果たしているんだな、ということを押さえておきましょう。


全体像の振り返り

ざっくりとではありますが、Laravelがリクエストを捌く上で重要な要素を見てきました。忘れないよう、簡単に復習しておきましょう。

  • Laravelの機能を表現するオブジェクトを生成して取り出す処理は、Service Containerが担当
  • Service Containerを利用し、どんなオブジェクトをつくっていくかはService Providerが管理
  • Kernelが司令塔となってそれぞれの処理を呼び出し、リクエストからレスポンスをつくっていく

Laravelが何をしているのかは、ぼんやりと見えてきました。
よってここからは、具体的にどうやってリクエストを処理しているのか、Laravelの仕組みを覗いてみます。

ソースコード探検

Laravelのソースコードを探検しながら、仕組みを解き明かしていきます。すべてを読むと迷子になってしまうので、雰囲気を掴める程度に見てみましょう。

エントリーポイント(public/index.php)

最初の一歩として、リクエストを扱う処理の入り口である public/index.phpから始めます。
nginxなどのWebサーバを利用していれば、このファイルがエントリーポイントであるのもなんとなくイメージできるかと思います。
一方、開発するときによく実行する php artisan serveコマンドとは結びついていないように見えます。このコマンドの仕組みもこの場で解明したいところですが、入り込むと少し長くなってしまうので、補足に譲ることにします。興味があったら見てみてください。

それではソースの中身を覗いてみましょう。

Auto Loader

最初に、Laravelアプリケーションに登場するPHPクラスファイルを読み込めるよう、Auto Loaderを読み出しておきます。

参考

Applicationクラス

続いて、 bootstrap/app.phpファイルを読み出しています。

以降ではapp.phpへ制御が移ります。

Service Containerの生成(app.php)

app.phpで最も重要な処理は、以下のコードです。

Applicationなるオブジェクトをつくっています。これは、Service Containerを表しています。
Service Containerの理解を深めるためにも、ざっくりと何をしているのか探ってみましょう。

Applicationクラス(Service Container)

まずは呼び出されていたコンストラクタを確認してみます。

色々重要そうな処理はありますが、いきなり細部に入り込むのはやめておきます。
ここで書かれた処理をイメージできるようになるには、Service Containerの大まかな仕組みを理解しておかなければなりません。
そうなると、Service Containerの理解を深めるために何から手をつければ良いか迷ってしまいそうです。しかし実は、後続のコードにService Containerの仕組みを読み解くのにうってつけなものが書かれています。

少し先を見ておきましょう。

復習になりますが、Service Containerの役割は、Laravelアプリケーションで必要なオブジェクトを用意・提供することです。
上で読んだ処理は、まさに役割を体現したようなことが書かれています。
見えやすくなるよう該当する処理だけ抜き出してみます。

Application::singleton()は、Kernelオブジェクトを要求されたとき、どのオブジェクトを用意するのか決めています。
そして、 Application::make()は、上の対応づけをもとに、オブジェクトを生成して呼び出し元に返しています。

オブジェクトを用意・提供している処理がどうやって実現されているのか見えてくれば、Service Containerが何をしているかも明らかにできそうです。
ということで、Service Containerを理解する一歩目として、オブジェクトの登録・取得処理の仕組みを探検してみます。

Service Containerの仕組み

Service Containerがどのように動いているのか、ざっくりと読み解いていきます。
まずは全体像を掴むために、やっていることを簡単に言葉にしておきます。
Service Containerは、自身のプロパティにオブジェクトをつくる方法を保存しておきます。
そして、インスタンスを要求されると、プロパティを参照し、オブジェクトを組み立てて返却します。

文字で書き出すとなんだか難しそうです。何をやっているのかもう少し見えやすくなるよう、それっぽいコードで動きを書き出してみます。

オブジェクトのつくり方をあらかじめ決めておき、必要になったらつくって渡す。これがService Containerの主要な処理です。
それではLaravelでは具体的にどんなコードで実現しているのか、解き明かしていきましょう。

Container::singleton()-オブジェクトのつくり方を登録

app.phpでは、オブジェクトのつくり方を登録する処理として Container::singleton()を呼び出しています。
この処理がどうやって動作しているのか追ってみたいと思います。

まずはメソッド定義を入り口とします。
※ Service Containerとしての機能は、Applicationクラスの継承元である Illuminate\Container\Containerクラスに書かれています。よって、以降ではContainerクラスを中心に見ていきます。

Container::bind()へと続きます。
今回の場合、変数abstractはInterface名・concreteはオブジェクトをつくる対象の完全修飾クラス名・そして第3引数はオブジェクトをシングルトンとして共有することを表現しています。

Container::bind()

オブジェクトの生成方法を決定するメソッドを見てみます。

今回はクラス名の文字列だけを渡しているので、オブジェクトの生成方法はService Containerが規定しているデフォルトが設定されます。
デフォルトの設定方法は Container::getClosure()に定義されているので、中身を確認しておきます。

関数を返す関数でぱっと見難しそうですが、ひとまずはオブジェクトをつくるときに Container::resolve()を呼び出しているんだな、ということだけを覚えておきましょう。

compact関数は変数名から配列を生成するためのものです。
参考

ですので、bindingsプロパティは以下のような構造となります。

つまり、 Container::bind()が呼ばれると、オブジェクトをどうやってつくるのかが決定されます。

Container::make()

app.phpで Container::singleton()を呼び出すことで、オブジェクトのつくり方が定まりました。
そして、呼び出し元のindex.phpで早速オブジェクトが必要になったので、 Container::make()でオブジェクトを取り出しています。

つくり方をもとに、Service Containerがどうやってオブジェクトを生成しているのか、探ってみましょう。


まずは例のごとくメソッド定義へ移動するところから始めます。

引数abstractには、クラスの完全修飾名を表す文字列 Illuminate\Contracts\Http\Kernelが指定されています。
オブジェクトをつくるときの引数parametersは今回は空なので、気にしないでおきます。

さて、実際にオブジェクトを組み立てている Container::resolve()を読んでいきます。

Container::resolve()

少し長めの処理なので、段階的に見ていきます。
まず、 Container::resolve()の目的は、指定された情報(今回はクラス名)をもとにオブジェクトをつくって返すことです。
これを実現するために、binding、つまりつくり方を探し出し、得られたものに従ってオブジェクトを組み立てています。
それぞれの動作を簡単に見てみます。

オブジェクトのつくり方を読み出す-Container::getConcrete()

※ contextualConcreteなるものは、参照元に応じて返すオブジェクトを切り替えるための仕組みです。今回は関係しないことから割愛します。

concrete変数はnullと評価されるので、 Container::getConcrete()が呼ばれます。
このメソッドでやっていることは非常にシンプルで、 Container::bind()で登録したClosureを読み出しています。登録されたClosureはオブジェクトのつくり方を表しています。

オブジェクトをつくる-Container::build()

つくり方を表すレシピが手元に揃ったので、いよいよオブジェクトをつくる処理を探ります。
ここから少し難しくなるので、流れを1つ1つじっくりと見ていきます。

Container::isBuildable()はconcrete(getConcreteメソッドで得られたもの)がClosureであることからtrueが返ります。
よって、インスタンスをつくる処理は、 Container::build()が主要な役割を担っています。

ということで、 Container::build()が何をしているか、明らかにしていきます。

どうやら引数がClosureであった場合、呼び出してすぐに値を返してしまうようです。
この処理を詳細に読み解こうとすると、関数の中で呼ばれる関数の戻り値の戻り値を考えるようになり、非常に難しくなってしまいます。
よって、ここでは、最終的にどうなるのか結果だけを書いておきます。なぜこのような構造を持ち、何が起こっているのかは補足に譲ることにします。

一連の処理は最終的に、具体的なクラス名 App\Console\Kernelを引数に Container::build()を呼び出すことで元の処理に合流します。

補足: Closureが呼ばれてから元の処理に合流するまで何が起こっているのか

しっかりと理解しておきたい方のために、簡単に処理の流れを記しておきます。
まず、そもそも呼び出されるClosureがどのようなものであったか復習するところから始めます。

なんと、 Container::resolve()の中でさらに Container::resolve()が呼ばれています。
なんだか無限ループしそうに見えますが、実際には呼ばれるときの引数が異なります。
Closureで呼び出す Container::resolve()は、具象クラス名 App\Console\Kernelを引数とします。
一方、 Container::make()から呼ばれるときは、Interface名 Illuminate\Contracts\Console\Kernelが渡されます。

この違いは、 Container::build()へ渡す引数である、変数concreteを組み立てる処理に表れます。
変数concreteを生成しているメソッド Container::getConcrete()を見てみましょう。

引数である具象クラス名 App\Console\Kernelはbindingsプロパティに登録されていないことから、そのまま戻り値となります。
すると、そのまま後続へと処理が進んでいき、 Container::build()がClosureではなく、具象クラス名 App\Console\Kernelを引数に呼ばれるようになります。

補足: なぜこのような周りくどい仕組みが存在しているのか

Container::build()を見ていると、 Container::resolve()が2回呼ばれていることが分かりました。
なぜこのような周りくどい処理をしているのでしょうか。
単純に考えれば、以下のコードのようにClosureの中で Container::build()を呼べば済む話のように思えます。

実は、Laravelのコードのコメントに経緯は書かれていました。該当箇所を見てみます。

どうやら、「extending」なる処理の都合でこのような複雑な構造が必要であるようです。

参考

詳細は参考リンクに譲りますが、既にできあがったオブジェクトを利用し、さらに処理を拡張したオブジェクトをつくることをextendingと呼ぶようです。
このとき、既存のオブジェクトを参照する上で、 Container::resolve()が既存のオブジェクトと対応するクラス名でも呼ばれるようになっていると、シンプルに扱えるようになります。

具体的には、以下のコードにて既存のオブジェクトが参照されます。


今度こそオブジェクトをつくる-Container::build()

さて、 Container::build()は呼び出す処理が少々複雑であることから回り道をしてしまいました。
ようやく本筋の処理に戻って来たので、改めて見るべきことを整理しておきます。

ここからは、 App\Console\Kernelオブジェクトがどうやってつくられるのか、たどっていきます。
まずはコードから流れを掴んでおきます。

Container::build()の動作は一言でまとめると、動的にオブジェクトを生成することです。
動的にクラスを操作するために、Reflectionと呼ばれる仕組みを呼び出しています。

参考

対象のクラスがコンストラクタで更にクラスを参照していると、少し処理が複雑になりますが、ここではひとまずクラス名をもとにオブジェクトをつくっているんだな、ということを押さえておきましょう。

戻り値を返すまで-Container::resolve()

Container::build()にて、欲しかったオブジェクトを手にいれることができました。
あとは呼び出し元に返すまでの流れを簡単に見ておきましょう。

注目すべきは、 Container::singleton()経由でオブジェクトを登録していた場合、Containerクラスのinstancesプロパティへオブジェクトを保存しているところです。
このことから、Service Containerはオブジェクトを取り出すとき、シングルトンである場合は一度つくったものを共有し、通常は新しくオブジェクトをつくって返していることが分かります。


Service Containerの概要ざっくりまとめ

とても長い道のりでしたが、ようやくService Containerの全体像が見えてきました。
忘れないよう理解したことを簡単にまとめておきましょう。

  • Container::singleton()Container::bind()でオブジェクトのつくり方をService Containerに保存しておく
  • Container::make()が呼ばれると、つくり方に基づいてオブジェクトを生成

つくり方をあらかじめ決めておいて、必要になったらつくる。言葉にするとシンプルですね。

Service Containerのコンストラクタ

さて、Service Containerの仕組みを見てきたのは、コンストラクタで何をしているのか理解するためでした。
ここまでの道のりを思い出すために、見ていた処理をもう一度振り返ります。

これまでは、Service Containerの仕組みを理解するために、その先の処理である App\Http\Kernelオブジェクトをつくるところを見てきました。
仕組みが見えてくればApplicationクラスのコンストラクタも読めるようになっているはずです。
早速中身を覗いてみましょう。

コンストラクタで肝となるのは、registerから始まる3つのメソッドです。
それぞれが何をしているか把握できれば、Service Containerへ入門できたと思って良さそうです。
概要を探っていきます。

Application::registerBaseBinding()

ここでのbindingsは、オブジェクトまたはオブジェクトのつくり方を表します。汎用的な機能を表すオブジェクトをService Containerへ登録しておくのがメソッドの役目です。

$this->singleton(Mix::class);のように引数が1つしかない場合、Service Containerのデフォルトの挙動にて、そのままオブジェクトがつくられます。

一方、PackageManifestクラスに関する設定は少し複雑そうです。何をやっているのか言葉にして整理しておきましょう。
第2引数がClosureであることから、bindings(インスタンスのつくり方)へそのまま登録されます。

$app->make(PackageManifest::class)のようにオブジェクトを取り出すタイミングにて、Closureが実行されます。
より具体的には、Closureに記述された方法でPackageManifestオブジェクトがつくられ、そのまま呼び出し元に返されます。

さまざまな呼び出し方を見ることで、Service Containerの仕組みも少しずつ見えてきたのではないでしょうか。

Application::registerCoreContainerAliases

※ Service Provider周りの話は長くなるので、先にこちらを見ておきます。
ここでの処理は、Service Containerに登録されたオブジェクトに別名をつけています。

具体例で見てみましょう。
先ほど見ていた Application::registerBaseBinding()にて、appという名前でApplicationクラスのインスタンスを登録していました。
今回の処理と合わさると、以下のコードのように動作します。

別名をつけておくことで、Interface名など、ほかの識別子でもオブジェクトにアクセスできるようになります。

Application::registerBaseServiceProviders()

このメソッドは、 Application::register()を呼び出して各種Service Providerを設定しているようです。

Service Providerは、Service Containerが掴めていればサクッと理解できるはずです。
ということで、ここからはService Providerについて詳しく見ていきます。

Service Provider

Service ContainerとService Providerが連携している処理を見ていくことで、Service Providerの仕組みを解き明かしていきます。
上で見たコードでは3つのService Providerが登録されているようですが、今回はリクエストを扱う処理と関係が深そうなRoutingServiceProviderに絞って見ていきます。

復習-Service Providerとは

Service Providerの仕組みを見ていく前に、役割を簡単に復習しておきます。
Service Providerは、アプリケーションが動作するのに必要な機能を用意するのが役目です。具体的に何をしているのか、それっぽいコードから動きを確認しておきます。

Service Providerを通して、機能を表現するオブジェクトをService Containerへ登録しています。
こうしておくことで、Laravelアプリケーションが動作するための準備を整えることができます。

Application::register()

Service Providerの全体像が見えてきたところで、仕組みへと入り込んでいきます。
まずはService Providerを参照している Application::register()を見てみます。

Application::register()の処理は、大きく2つに分かれています。
1つはbootと呼ばれる処理なのですが、入門段階ではあまり触れる機会が無さそうなので、補足へ譲ります。

重要なのは、もう1つの処理である、 ServiceProvider::register()を呼び出しているところです。

ServiceProvider::register()

ここでは、 ServiceProvider::register()が何をしているのか改めて見ていきます。具体例として、RoutingServiceProviderを対象とします。

RoutingServiceProvider::register()は、たくさんのregisterから始まるメソッドを呼び出しています。
今回は代表例として、一番上のメソッドだけを覗いておきます。

Service Providerの役割の通り、Service ContainerへLaravelアプリケーションの機能を表現するオブジェクトのつくり方を設定しています。
繰り返しになりますが、Service Providerはひとまずは、Laravelアプリケーションの機能を表現するオブジェクトのつくり方を登録していくのが責務である。ということを頭に入れておきましょう。

補足: ServiceProvider::boot()について

Service Providerではもう1つ、bootなるメソッドが呼ばれるようになっています。

参考

これは、Laravelの設定ファイルなどが読まれた後、つまりLaravelアプリケーションが動き出す準備が整ったときに呼び出されます。
あまり使う機会は多くありませんが、例えばほかのService Providerで登録されたオブジェクトで何か処理をしたいなど、自分自身以外と連携するときに有用です。
更に、今回見ていく処理で具体例を挙げると、 routes/web.phpに書いた Route::get()などで設定した情報を読み出すときにboot処理が関わっています。

ほかの機能と連携するための仕組みが用意されているんだな、ということだけまずは意識しておきましょう。


復習-Laravelが動く前準備が整うまで

さてさて、Laravelアプリケーションの準備段階で既に途方もない旅路となってしまいました。
ここからいよいよ実際にレスポンスを組み立てるところに移れるのですが、その前に来た道を振り返っておきましょう。

具体的には、Laravelアプリケーションのエントリーポイントから前準備が終わるまでを通しで見ていきます。
Service Container・Service Providerの役割が頭に入っていれば、きっと流れが掴めるはずです。

コードに書かれていることを箇条書きでも整理しておきましょう。

  • Auto LoaderでPHPクラスを読み込む準備をしておく
  • Service Containerを用意
  • Laravelの基本的な機能をService Providerなどを介してService Containerへ登録
  • Laravelアプリケーションの核となるKernelオブジェクトをService Containerから取り出せるよう設定

Laravelアプリケーションの準備段階の処理を見てきたことで、残すところ理解したいコードは以下の3行だけです。

当然、内部ではたくさんの処理が呼ばれているので、要点をかいつまんで少しずつ読み解いていきましょう。

Request::capture()

まずは、HTTPリクエストを表すオブジェクトをつくっている処理です。LaravelではどうやってHTTPリクエストを扱おうとしているのか、理解することを目指します。
見ていく処理は変わりましたが読み方は変わらず、メソッド定義へ移動するところから始めます。

重要なのは、PHPDocに書かれている文です。
LaravelにおけるHTTPリクエストオブジェクトは、server variablesなるものからつくられているようです。
ここでのserver variablesは、 $_GET$_POSTのようなWebサーバから受け取ったものを元に構築されているものを指します。

つまり、いわゆるPHPにおけるSuperglobalsから生のHTTPリクエストの内容を参照しています。

参考

Symfonyとは

先ほどのコードにもあった通り、リクエストオブジェクトは、SymfonyRequestと名付けられたオブジェクトをもとにしているようです。
ゆえに、LaravelのHTTPリクエストオブジェクトがどのようなものであるか理解するには、Symfonyが何か明確にしておかなければなりません。

Symfonyそのものは、Laravelと同じくWebアプリケーションフレームワークです。

参考

フレームワークの中で別のフレームワークをもとにしていると考えると、なんだか混乱してきそうです。もう少し詳しく見てみます。

LaravelとSymfonyの関係

Symfonyは、HTTPリクエストやRouterなど、Webアプリケーションに共通する要素を独立して扱えるよう切り出した、Symfony Componentsを提供しています。
参考

そしてLaravelでは、一部の機能をSymfony Componentsを拡張することで実現しています。
参考


まとめると、LaravelはHTTPリクエストオブジェクトを、Symfony Componentsとして提供されているものを拡張することで実装しているようです。
LaravelはSymfony Componentsを利用することで、いわゆる車輪の再発明を経ることなく、Webアプリケーションフレームワークとして必要な機能を実現しています。

SymfonyRequest::createFromGlobals()

Symfonyの正体が見えてきたので、呼ばれる処理を順に追いながら、リクエストオブジェクトがつくられる仕組みを見ていきます。
まずはSymfonyがどうやってHTTPリクエストオブジェクトをつくっているのか、俯瞰しておきます。

Webサーバから渡される情報をもとにPHPが構築する変数Superglobalsから、リクエストオブジェクトを作成しています。
ここでは、リクエストオブジェクトがあることで、クエリ文字列やPOSTボディを扱いやすくなっているんだな、ということを押さえておきましょう。

Request::createFromBase()

Symfonyのリクエストオブジェクトは出来上がったので、続いてはLaravelが扱うリクエストオブジェクトへどうやって変換されていくのかを見ていきます。
流れを思い出すために、もう一度 Request::capture()を見直しておきます。

どうやらSymfonyが提供しているHTTPリクエストオブジェクトをベースに、Laravelが扱うHTTPリクエストオブジェクトをつくり出しているようです。
実際の処理の流れも確認しておきましょう。

クエリ文字列やクッキーといったHTTPリクエストに関する情報をSymfonyのリクエストオブジェクトから、Laravelのリクエストオブジェクトへ詰め替えています。
こうすることで、Symfony Componentsであるリクエストオブジェクトを拡張した、Laravelが扱うHTTPリクエストオブジェクトを手にいれることができます。

Kernel::handle

ここまでの処理で、アプリケーションを制御するKernelオブジェクトと、HTTPリクエストを表すリクエストオブジェクトがそろいました。
つまり、ようやくHTTPリクエストをもとにHTTPレスポンスを組み立てる処理を見ることができます。

ということで、 Kernel::handle()へと足を踏み入れていきましょう。

Kernel::handle()は、HTTPレスポンスオブジェクトをつくって返すのが責務のようです。レスポンス自体は、 Kernel::sendRequestThroughRouter()でRouterと呼ばれるオブジェクトを介して生成されています。
Routerがどんな役割を持つのか簡単に理解しておくために、今回アプリケーションコードとして書いたものを再度見てみます。

これは、 http://localhost:8000/helloのようなURLへリクエストが送られると呼ばれる処理です。
Kernel::sendRequestThroughRouter()はリクエストのパスから呼び出すべき処理を決定し、実際に呼び出した上で得られたレスポンスを出力します。
上の例であれば、Routerから得られた戻り値である文字列 Hello Laravel from helloをよしなに加工してレスポンスオブジェクトへと変換しています。

Laravelの処理がどうやってアプリケーションのコードと合流してレスポンスを構築していくのか、仕組みを探っていきます。


Kernel::sendRequestThroughRouter()

Kernel::handle()から Kernel::sendRequestThroughRouter()へ移ります。
ここから更に難しくなってきますが、要所を押さえて読み解いていきましょう。まずはメソッドを読んでみます。

大きく2つの処理に分かれているので、分解して見ていきましょう。

Kernel::bootstrap()

まずはbootstrap処理です。単語自体は自動実行するということを意味しますが、Laravelでは起動するぐらいの意味で捉えておくとよいかもしれません。
Laravelにおけるbootstrapとは、Kernelクラスのbootstrappersプロパティに設定されたクラスのbootstrapメソッドを呼び出すことです。

プロパティに設定されている完全修飾クラス名と対応するオブジェクトは、Service Containerを介してつくられています。

ここで、bootstrappersとして記述されたクラスは、 .envファイルを読み込んだり、configディレクトリ以下の設定値を読み出したりと、複雑なことをしています。
内部の仕組みが複雑であること・Hello World相当の処理では関わりが薄いことから、大半の処理は割愛します。

それぞれがざっくり何をしているかは、補足へ譲ることにします。

BootProviders

ここでは、以降の処理と関わりが深いBootProvidersだけを見ておきます。
とてもシンプルな処理なので、クラス全体を示します。

Service Containerのbootメソッドを呼び出しています。具体的に何をしているのか、Service Containerを覗いてみます。

どうやらLaravelが起動(boot)するときには、Service Providerのbootメソッドを一斉に呼び出しているようです。
リクエストを処理する直前なので、Laravelを起動させてリクエストを扱う準備を整えるには丁度いいタイミングだと言えそうです。

補足: Kernel::bootstrap()では何が起きているのか

ここでは、BootProviders以外のbootstrapperが何をしているのか、概略を記しておきます。
Laravelのどういう処理がどこで動いているのか知っておくだけでも後々役に立つはずなので、ざっくりと押さえておきましょう。

LoadEnvironmentVariables

.envファイルを読み出すのが役割です。
最終的には file_get_contents()で.envファイル読み出して、対応するオブジェクトから参照できるようにしています。

LoadConfiguration

configディレクトリ以下の *.phpファイルをrequire命令で読み出しています。該当するファイルは、キーを設定名・値を設定値とする配列を返却しているので、この命令により設定を表す配列が得られます。
参考

Laravelアプリケーションで設定を参照するときは、上の処理で得られた配列から値を取り出しています。

HandleExceptions

Laravelアプリケーションで例外が起きたとき、どのクラスに任せるかを登録しています。対象は、 App\Exceptions\Handler.phpです。
このように設定しておくことで、Laravelアプリケーションで起きた例外を一律に制御できるようになります。

RegisterFacades

Route::get()のような記法にて、Service Containerに登録されたオブジェクトを参照するための仕組みを実現するFacadeクラスを準備します。Facadeの概要は後ほど触れていきます。
ひとまずは、Facadeクラスのクラス変数へService Containerを登録しているんだな、ぐらいのことを覚えておきましょう。

RegisterProviders

config/app.phpに書かれたServiceProviderを中心に、各種ServiceProviderを読み出しています。
読み出されるService Providerは、キャッシュやセッション・バリデーション処理など、リクエスト扱う上で重要な責務を担っています。


Pipelineとは

bootstrap処理でLaravelが動き出す準備が完全に整いました。
そうなると、残りの処理はリクエストからレスポンスを組み立てるのに大きく関わっているはずです。早速中身を見ていきたいところなのですが、なにやら難しそうな処理が書かれています。もう一度コードを見ておきましょう。

Pipelineと呼ばれる仕組みで何かたくさんの処理が動いているようです。Pipelineとは一体どういうもので、何をしているのでしょうか。
pipelineという単語は、ガスなどを輸送するパイプを意味しています。Google画像検索なんかで見てみるとイメージしやすいかもしれません。

LaravelにおけるPipelineは、処理を繋げる役目を持っています。

Pipelineは何をしているのか

本来であればPipelineクラスのコンストラクタから始めて各メソッドを読んでいきたいところです。
しかし、Pipelineクラスは、おそろしいほど複雑な処理が書かれており、仕組みを解き明かすだけでも大変な労力を要します。

よってここでは、LaravelにおけるPipelineオブジェクトが何をしているのか、動作を簡単に記しておくに留めておきます。
一言で表すと、Pipelineオブジェクトは、複数のオブジェクトのメソッドを共通の引数でどんどん呼び出すように動きます。これだけではイメージしづらいので、上で見た処理を簡易版のクラスで再現してみます。

リクエストオブジェクトを引数に、ミドルウェア(後述)なるオブジェクトのhandleメソッドをどんどん呼び出します。そして、後処理として、 Kernel::dispatchToRouter()が呼ばれています。
ざっくり抜き出してみると、やっていることはシンプルですね。

このように、一定の入力を複数のハンドラで処理していく仕組みは、デザインパターンにてChain of Responsibilityパターンと命名されています。
もう少し詳しく知りたい方は参考リンクを読んでみてください。
参考

補足: ミドルウェアについて

ミドルウェアはHello Worldの段階ではあまり意識することはありませんが、せっかくなので少しだけ触れておきます。
参照されるミドルウェアは、 App\Http\Kernel.phpに書かれています。

これらのクラスの完全修飾名は、Service Containerを介してオブジェクトへと変換されます。
そして、ここに書かれているミドルウェアと名付けられたオブジェクトは、ミドル(中間)という名の通り、リクエストの前処理を担っています。

例えば、ValidatePostSizeクラスは、POSTリクエストのボディのサイズを事前に検証しておき、極端に大きなリクエストを扱わないようガードする役割を担っています。
具体的な実装もさらっと見ておきましょう。

入門段階では、ミドルウェアはリクエストオブジェクトを受け取って前処理をよしなにやっているんだな、ということを押さえておきましょう。

Kernel::dispatchToRouter

色々と回り道をしてきましたが、ようやくリクエストからレスポンスを組み立てる処理へたどり着きました。
本命の処理が何をしているのか、読んでいきましょう。

プロパティrouterは、Service Containerを介して渡された Illuminate\Routing\Routerオブジェクトです。
Routerはその名の通り、リクエストのURLをもとに対応する処理へと遷移先を振り分けるのが役割です。どうやって振り分けているのか、 Router::dispatch()から見てみます。

Router::dispatch

Kernelクラスから離れて、Routerクラスを探っていきます。
まずはdispatchメソッドの定義から始めていきます。

戻り値がHTTPレスポンスっぽいオブジェクトであることから、 Router::findRoute()で呼び出すべき処理を決め、 Router::runRoute()でHTTPレスポンスを生成しているように見えます。
それぞれの仕組みを見ていけば、HTTPレスポンスがどうやってつくられるのか明らかになりそうです。

順に整理していきます。

Router::findRoute()

最初に、呼び出す処理を決めているメソッドを読んでいきます。

リクエストのURLと合致するものとして、Routeなるオブジェクトを探し出しているようです。
ここで、Routeについて少し掘り下げていこうと思います。

Route

Routeという文字を見ると、Hello Worldアプリケーションとして実装したコードが思い浮かぶかもしれません。
復習がてら、アプリケーションの実装コードとして書いたものを見てみましょう。

Routeクラスのgetメソッドなる静的メソッドを呼び出しているっぽい処理が書かれています。これはRouteオブジェクトと関係が深そうです。
ここで、先ほどまで見ていたRouterオブジェクトとの関係を考えると、いくつか疑問が浮かんできます。

  • routes/web.phpで書かれたRouteとは何か
  • いつroutes/web.phpは呼ばれたのか
  • Route::get()が呼ばれると何が起こっているのか

Routerが何をしているのか理解するためには、これらの疑問を解消しておいた方が良さそうです。1つ1つざっくりと考えてみましょう。

RouteクラスとFacadeクラス

routes/web.phpで書かれたRouteクラスは、完全修飾名が Illuminate\Support\Facades\Routeとなっています。
Facadeなる概念が新しく登場しました。Facadeも掘り下げていくと深みにハマってしまうので、ここでは概要だけ触れておきます。

Facadeはデザインパターンの1つで、フレームワークなどの機能をシンプルに扱うためのインタフェースを提供します。
参考

これが何を表すかは、Facadeクラスの仕組みを知ることで見えてくるはずです。
深入りしすぎない程度にたどっていきましょう。

Facade

LaravelにおけるFacadeの仕組みを明らかにしていきます。
入り口はFacadeの1つであるRouteクラスとします。どのような構造を持つのか見てみましょう。

Route::get()が呼ばれているはずなのに、getメソッドが定義されていません。親クラスにも定義は無さそうです。
どういう仕組みで何を呼び出しているのでしょうか。

実は、継承元であるFacadeクラスにて、 __callStatic()なるメソッドが定義されています。
これはPHPが特定のタイミングにて呼び出すメソッドで、 Route::get()のように静的メソッドを呼び出そうとして対象が見つからなかったときに発火します。

参考

ということで、 __callStatic()から呼ばれている処理を見てみます。

中身を読んでみると、どうやらService Containerへ routerというキーで配列アクセスをしているようです。
Service ContainerであるContainerクラスは、ArrayAccess Interfaceを実装しているので、 Container::offsetGet()が呼ばれます。
参考

Container::offsetGet()は、オブジェクトを取り出す処理である Container::make()を呼んでいます。

つまりここまでの処理は、Service Containerからrouterというキーのオブジェクトを取り出すのが目的のようです。
routerというキーでオブジェクトを登録する処理は、 Application::registerBaseServiceProviders()から呼ばれるRoutingServiceProviderにて定義されています。

ここでのRouterは、先ほどまで見ていた Illuminate\Routing\Routerクラスを指しています。


少し長くなってしまいましたが、LaravelにおけるFacadeクラスは、デザインパターンで定義されている通り、Service Containerへ簡潔にアクセスするためのインタフェースを提供しているようです。
具体的には、 Route::get()のように書くだけで、Service Containerに登録されたRouterオブジェクトのメソッドが呼び出せるようになります。

routes/web.phpはいつ読み出されるのか

Facadeの仕組みを見ることで、 routes/web.phpがRouterと関わりが深いことが分かりました。
ですが、これまで見てきた処理でこのファイルは読み出されていませんでした。

重要な処理である以上どこかで読み出されてはいそうですが、一体どこで参照されたのでしょうか。
実は、Service Providerの1つである、 App\Providers\RouteServiceProviderクラスが入り口となっています。
※ このService Providerは config/app.phpに定義されているので、 Kernel::bootstrap()にて呼び出されています。

ここの処理は非常に複雑なので、とりあえず routes/web.phpはService Providerを介して読み出され、実行されている。ということを頭に入れておきましょう。

Route::get()は何をしているのか

たくさん寄り道をしてしまったので、本来の目的を整理しておきます。
今回の目的は、Routerから参照されているRouteなるオブジェクトがどのようにつくられているのか明らかにすることです。

ということで、関わりが深そうな Route::get()へと戻ります。

ここでのRouteの実体はRouterオブジェクトであるため、実際には Router::get()が呼び出されます。
何が起きているのか掘り下げていきましょう。

Router::addRoute()に注目してみます。
Routerが参照しているRouteオブジェクトをつくっている処理にたどり着けました。
ここを見ていけば、Routeオブジェクトが何者であるか理解できそうです。

先に進んでみましょう。

ここで、methodsはGETまたはHEAD・uriは Route::get()に渡したパス・actionは Route::get()に渡したClosureを表しています。
Routeクラスもさわりだけ見ておきます。

ひとまずはプロパティに引数で渡したものを保存しているぐらいに理解しておきましょう。


さて、ようやくRouteオブジェクトが何者であるか理解できました。
理解したことを整理しておくと、Routeオブジェクトは、 routes/web.phpに書かれた Route::get()のようなメソッドを介してつくられます。
そして、Routeオブジェクトはパスと実行する処理(Closure)を保存しています。

Router::findRoute()

Routeオブジェクトが見えてくると、以降の処理で何をするかも明確になります。
リクエストのパスと対応するRouteオブジェクトさえ手に入れば何を実行するべきかも定まるので、レスポンスまで一気に繋げることができます。

ということで、 Router::findRoute()でどうやってRouteオブジェクトを探し当てるのか、解き明かしていきましょう。
復習のためにメソッド定義をもう一度見ておきます。

該当するRouteオブジェクトを探す処理は、RouteCollectionオブジェクトが担っているようです。
※ 各Routeオブジェクトは、 Route::get()にてRouteCollectionオブジェクトへ詰め込まれています。

RouteCollection::match()

RouteCollectionオブジェクトがどうやってパスと対応するRouteオブジェクトを探しているのか、メソッドから仕組みを見てみます。

重要なのは、 Route::matches()がtrueを返したRouteオブジェクトを返却していることです。
つまり、Routeオブジェクトでどのように判定しているかが分かれば、パスからどうやって呼び出すべき処理を決定しているのかが見えてきます。

ということで、 Route::matches()が何をやっているのか探っていきましょう。

Route::matches()

Route::getValiators()は、validatorと呼ばれるオブジェクトを取得する処理です。
validatorはbool値を返すmatchesというメソッドを実装しているので、すべてのvalidatorが有効だと判定したRouteオブジェクトが返されるようです。

ここでは、パスと関わりが深いUriValidatorクラスを見てみます。

何やら難しそうな処理が書かれていますが、ここではリクエストのパスとRouteオブジェクトに登録されたパスをよしなに比べているんだな、と思っておきましょう。


Router::findRoute()

ざっくり探っていくことで、何が起きているのか見えてきました。
理解を整理するために、 Router::findRoute()の流れをまとめておきましょう。

処理の流れが見えやすいよう、箇条書きで簡単に振り返っておきます。

  • routes/web.php`などで`Route::get()のような処理を呼び出す
  • パスと実行する処理を表すRouteオブジェクトを登録
  • Routeオブジェクトは、RouteCollectionオブジェクトによって保持される
  • RouteCollectionオブジェクトは、HTTPリクエストのメソッドやパスの情報をもとにRouteオブジェクトのmatchesメソッドを呼び出す
  • それぞれのRouteオブジェクトは、パスなどの情報をもとに、自身でリクエストを処理するべきか判定
  • 合致したものが返され、 Route::findRoute()の戻り値となる

ということでまとめると、パスが合致するRouteオブジェクトを探し出すのが Router::findRoute()の責務だったようです。

Router::runRoute()

Router::findRoute()により、実行するべき処理に対応するRouteオブジェクトが得られました。
あとはリクエストを扱う処理を呼び出せばレスポンスが手に入るはずです。流れを見返すために、 Router::findRoute()を呼び出しているところを見直しておきましょう。

リクエストオブジェクトと、 Router::findRoute()`で得られたRouteオブジェクトを引数に、`Router::runRoute()を呼び出しているようです。
Router::runRoute()の仕組みが明確になれば、リクエストからレスポンスを組み立てるまでの処理の流れが繋がるはずです。

最後の正念場となりそうですが、じっくりと見ていきましょう。

再び処理が2つに分かれているようです。メソッド名を見るに、 Router::runRouteWithinStack()`でレスポンスの元になるオブジェクトを組み立て、`Router::prepareResponse()でHTTPレスポンスを表すオブジェクトへと変換しているように思えます。
ということなので、まずはレスポンスの元をつくっていそうな Router::runRouteWithinStack()を見ていきます。

Router::runRouteWithinStack()

長そうなメソッド名ですが、ひとまず定義から概要を俯瞰しておきます。

ここでのミドルウェアは、Routeオブジェクトに挟まるものなのですが、入門段階ではあまり触れる機会も無いので割愛します。
Pipelineで書かれた処理は、ミドルウェアの処理を実行した上で後処理を動かすためのものです。

今回見るべきは、Routeオブジェクトと結びつく処理を呼び出していると思われる Route::run()です。
Routeオブジェクトと結びつく処理は、 routes/web.phpで定義したものです。肝となる重要な処理なので、再掲しておきます。

このClosureがどうやって呼び出されるのか理解することを目指します。

Route::run()

ということで、Routeオブジェクトへと移動します。
Route::run()が何をしているのか見ていきましょう。

Route::runCallable()へと進みます。

ここからはじっくり整理していく必要がありそうです。
1つ1つの処理が何をしているのかゆっくり解き明かしていきましょう。

CallableDispatcher

Service Containerから取り出そうとしていたCallableDispatcherオブジェクトは、Service Containerの初期処理で実行されるRoutingServiceProviderにて用意されています。

ここで生成されるオブジェクトは、 Illuminate\Routing\CallableDispatcherオブジェクトです。
ということで、Service Containerから何を取り出しているのか見えてきました。

続いて、取り出されたオブジェクトのdispatchメソッドで何が起きているのか見ていきます。

CallableDispatcher::dispatch()

ついに Route::get()で渡した処理を呼び出している箇所へたどり着きました。

最後に明らかにしなければならないのは、どうやって引数を組み立てているかです。ここで渡されるClosureは、リクエストオブジェクトを引数として受け取っています。
よって、 CallableDispatcher::resolveParameters()にて、なんらかの仕組みで引数であるオブジェクトを構築しているはずです。

ここは難解な処理が書かれているので、全体像を掴むぐらいを目指して見ていきます。

CallableDispatcher::resolveParameters()

まずはメソッド定義を見てみます。
1行の処理ですがたくさんのことが詰まっているので、じっくり解きほぐしていきましょう。

まず、 Route::parametersWithoutNulls()`は、`/user/{id}のようなパスから組み立てられた引数を抜き出す処理です。
参考

このようなパラメータは今回は扱わないので割愛します。

続いて、ReflectionFunctionオブジェクトをつくっている処理は、関数が受け取る引数を読み出すためのものです。
Route::get()に渡した処理を例に考えると、Request型のオブジェクトを引数として受け取ることが読み出されます。

これらの前提を踏まえた上で、Closureへ渡す引数を組み立てる処理 CallableDispatcher::resolveMethodDependencies()を読んでいきましょう。

CallableDispatcher::resolveMethodDependencies()

難しそうな処理が並んでいます。一気に理解しようと思うと迷子になってしまうので、まずは何をやっているのかざっくり掴むことを目指します。
そこで、処理が何をしているのか簡易版のコードで眺めておきます。

Reflectionの仕組みが関わることで難しく見えますが、ざっくりやっていることは、関数定義をもとに引数を組み立てているのみです。
よって、どうやって引数となるオブジェクトを組み立てているのか、流れを押さえておきましょう。

CallableDispatcher::transformDependency()

オブジェクトをつくっていそうな処理を見てみます。

思いのほかシンプルに動いていそうです。型ヒントで欲しいオブジェクトの型を指定していたことで、Service Containerからクラス名をもとにオブジェクトを取り出すことができます。
リクエストオブジェクトはService Containerに詰め込まれていることから、問題なく取り出されます。

Closure呼び出し

流れが見えてきたので、呼び出し元をもう一度見てみます。

難しそうな処理が並んでいましたが、流れを整理してみると理解できそうです。簡単にまとめておきましょう。

  • Reflectionの仕組みを利用して、Closureの引数情報を読み出す
  • 引数を1つ1つループで走査し、型ヒント情報からService Containerを介してオブジェクトを生成
  • 得られたオブジェクトを引数リストを表す配列へ詰め込んで返却

更に戻って、Closureを呼び出しているところへと移ります。

得られたオブジェクトを引数にClosureを呼び出しています。
ということで非常に長くなりましたが、パスと対応する処理を呼び出すまでの流れをようやく掴めました。

Router::runRoute()

ここまでの処理を経ることで、文字列 Hello Laravel from helloがレスポンスとして得られました。

このままではHTTPレスポンスオブジェクトとして返せないので、よしなに変換している処理を見ていきたいです。
ということで、レスポンスを整形している処理がありそうな呼び出し元へと戻りましょう。

Router::prepareResponse()でどうやってHTTPレスポンスへと変換されているのか、整理していきます。

Router::prepareResponse()

まずはメソッド定義から全体像を見ておきます。

JSONやレスポンスっぽいオブジェクトなど、さまざまなオブジェクトをもとにレスポンスが組み立てられるよう、たくさんの分岐が書かれています。
今回のレスポンスは単なる文字列なので、最後の分岐を通ります。

文字列をもとに、Symfonyが提供するResponseクラスを拡張した、 Illuminate\Http\Responseオブジェクトを生成しています。

Response::send()

あとは呼び出し元にレスポンスオブジェクトを返すだけなので、エントリーポイントまで戻ります。

最後に Response::send()を呼び出して終わりのようです。
何をしているのか見ていきましょう。

最終的な仕組みはおどろくほどシンプルでした。
PHP標準のecho命令を呼び出すことで、WebサーバへHTTPレスポンスを出力しています。

あとはWebサーバが受け取ったレスポンスをよしなに処理していけば、ブラウザへ文字列が表示され、無事LaravelのHello Worldが達成されます。

振り返り

Hello Worldのように単に画面に文字列を表示させるだけの処理でしたが、とてつもなく長い道のりでした。
歩んできた道を忘れないよう、箇条書きで流れをまとめておきましょう。

  • public/index.phpがエントリーポイント
  • autoloaderでクラスを読み出せるようにしておく
  • オブジェクトを用意・提供するService Containerを初期化
  • あわせて、どういうオブジェクトをつくるか規定したService Providerと、Service Containerを連携させながらLaravelが動作するのに必要なオブジェクトを用意
  • アプリケーションの制御を担うKernelオブジェクトをService Containerを介して生成
  • Kernelオブジェクトのhandleメソッドを呼び出す
  • routes/web.phpに書いたHello World処理をもとに、パスと実行する処理を表すRouteオブジェクトを作成
  • リクエストのパスと合致するRouteオブジェクトを探し、対応する処理が呼ばれる
  • Router・Routeオブジェクトを介してつくられたレスポンスをPHPのecho命令で出力することで、Hello Worldに相当する文字列が画面に表示される

これまでたどってきた処理、とくにService Container・Service ProviderはLaravelでアプリケーションをつくる上でとても重要です。
普段あまり触る機会が多くなくても、どういう仕組みで動いているのか、ざっくりとでも頭に入れておきましょう。


以降では、ちょっとした補足を残しておきます。

補足: artisan serveコマンドはいかにして動作しているのか

これまでは、 public/index.phpをエントリーポイントとした処理を見てきました。
しかし、開発するときには php artisan serveコマンドで開発サーバを動かす機会も多いはずです。このコマンドはどのように動いているのか、ものすごくざっくりとですが整理しておきます。

まずは全体像を描くために、やっていることを簡単に書き出しておきます。

  • artisan serveコマンドによりPHPをビルトインサーバモードで起動
  • router scriptとしてservrer.phpを指定
  • server.phpにより public/index.phpが読み込まれる
  • ※ cwdはProcessクラスにてpublic以下が指定される
  • あとはnginxなどと同じような流れでリクエストを処理

エントリーポイントは、 .env`などが置かれているディレクトリにある`artisanファイルです。実体はPHPファイルなので、PHPスクリプトとして実行されます。
中身のソースコードも馴染みがあるはずです。中を見ていきましょう。

Service ContainerやService Providerで準備を整え、Kernelで処理を実行。といったように public/index.phpで見たものと似通っています。
ここからの処理を詳細に見ていくと補足に収まり切らなくなってしまうので、重要な処理まで移動します。

Console KernelはService Providerなどを介して、実行する処理を表現するCommandオブジェクトを読み出します。
そして、コマンドライン引数(今回はserve)と対応するオブジェクトを実行するべき処理として呼び出します。

一気にまとめてしまうと、 ServeCommandが今回実行する対象となります。

コマンド名を表すプロパティを見ても、コマンドライン引数 serveと対応していることが分かります。
ServeCommandオブジェクトもたくさんの処理を担っているのですが、処理の流れに関わるところだけ見ておきます。

難しそうな処理が並んでいますが、やっていることを抜き出すと、PHPを開発サーバとして起動しています。
そして、パスに応じてリクエストを振り分ける処理として、 server.phpなるファイルを指定しています。

参考

このファイルに artisan serveコマンドの正体が詰まっているので、中身を読み解きます。

今まで見てきた