PHP
Laravel

Laravelのリクエスト開始からコントローラにたどり着くまで


Laravelの 0 -> 1 を理解してみよう☆のコーナー

社内勉強会用に昔書いたやつをコピペっておく。

L55くらいの時代に書いたやつ。


STEP 1 Web request has come

ブラウザ -> なんやかんや -> ELB -> EC2 -> Nginx -> php-fpm ここまではみんな一億%くらい挙動を理解してると思うのでスキップ

何はともあれ public/index.php が必ず最初に呼び出されます

https://github.com/laravel/laravel/blob/v5.5.28/public/index.php


STEP 2 Composer autoloader

require __DIR__.'/../vendor/autoload.php';

https://github.com/laravel/laravel/blob/v5.5.28/public/index.php#L24

Composerのautoloaderが呼び出されます

composer.json のオートロード設定はこんな感じ

"autoload": {

"classmap": [
"database"
],
"psr-4": {
"App\\": "app/"
},
"files": [
"app/helpers.php"
]
},

ちなみに autoload の登場以前のPHPは何をしていたかというと、 require_once __DIR__ . '/app/App.php' みたいなのを毎回かいてたよ。ヤバイよね。


autoload classmap database

classmap で定義されているのはディレクトリパスで、あとは何かいい感じにしてくれるよ。

vendor/composer/autoload_classmap.php が生成されて、その中身みるとこんな感じ。

return array(

'AddXxx' => $baseDir . '/database/migrations/2015_12_29_113041_add_xxx.php',
'AddYyy' => $baseDir . '/database/migrations/2016_11_11_211153_add_yyy.php',

database ディレクトリ以下にあるPHPファイルをパースして、クラス名とファイルパスのマッピングを作ってくれるんだね!

だからマイグレーションファイルのクラス名が重複するとこの辺で死ぬよ!


autoload psr-4 App

PSR-4 で定義されたルールにのっとってロードしてくれるよ!

App\\ から始まるネームスペースのクラスは app/ ディレクトリをルートとしているよ!って感じ

App\Hoge\Fuga をロードしようとすると app/Hoge/Fuga.php があるはず・・・!という気持ちでオートロードしてくれるよ。


autoload files

app/helpers.php を必ず事前にロードしてくれるよ。

他のは「このクラス名がきたらこのファイルをrequire_onceする」っていう遅延ロードだけど、これは同期ロードだよ。すごいね。

だからどこにも require_once __DIR__ . '/app/helpers.php'; なんて書いてないのヘルパー関数が動くなだね。すごいね!!


STEP 3: bootstrap

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

https://github.com/laravel/laravel/blob/v5.5.28/public/index.php#L38

ついにLaravelが作られるね・・・!

Bootstrapファイルを読み込むよ。


Create Application

$app = new Illuminate\Foundation\Application(

realpath(__DIR__.'/../')
);

https://github.com/laravel/laravel/blob/v5.5.28/bootstrap/app.php

これがLaravelの全ての起点だよ!!!

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Application.php

public function __construct($basePath = null)

{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}

protected function registerBaseBindings()
{
static::setInstance($this);

$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}

protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}

public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
// 略
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Application.php#L138-L149

割と大事なのが class Application extends Container implements ApplicationContract, HttpKernelInterface のとこで、

ApplicationContainer を継承しているやね

つまり、 Application 自信がコンテナとして動くので、 app(XxxClass::class) みたいにしたときに、 Application がいろんな解決をすることができるんだね。すごいね。

細いことはいろいろあるけど、基本的にはこの段階で「Laravelの起動に最低限必要なクラス群を読み込めるようにする準備」として「Application自身にいろんなものを設定する」ことをしているよ。

https://github.com/laravel/laravel/blob/v5.5.28/bootstrap/app.php#L29-L37

その他↑この辺はきっと何か似たような登録処理をしているイメージ。

このファイルはフレームワーク側じゃないので、何かいろいろ初期化処理したかったらここに書くのはワンチャンあるよ。

ほぼほぼほぼほぼのケースでは、ちゃんと書くべき場所が別にあるからレアケース。

$app->singleton(

Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

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

ここは比較的重要で、「WEBリクエスト用のKernel」と「Console(Workerとかコマンドとか)用のKernel」がそれぞれ別にあるんだね


Step4: Create Kernel

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

https://github.com/laravel/laravel/blob/v5.5.28/public/index.php#L52

ここでは bootstrap/app.php

$app->singleton(

Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

されているので App\Http\Kernel.php が呼び出される。 `

public function __construct(Application $app, Router $router)

{
$this->app = $app;
$this->router = $router;
$router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Http/Kernel.php#L89-L103

HTTPのKernelにおいては、 Middlewareの登録処理が行われてることがわかりますね

どういうMiddlewareが登録されているかはApplication側で定義されている感じ

https://github.com/laravel/laravel/blob/v5.5.28/app/Http/Kernel.php


Handle Request

$response = $kernel->handle(

$request = Illuminate\Http\Request::capture()
);

https://github.com/laravel/laravel/blob/v5.5.28/public/index.php#L54-L56

Illuminate\Http\Request::capture() の中身をみると、

$_GET とか $_POST とかのリクエスト系の処理をもとにSymfony\Requestを作られて、それをラップしたIlluminate\Http\Requestを作る感じ!

そうやって作った Request オブジェクトを $kernel->handle() する感じ。


WHAT IS KERNEL HANDLE

    /**

* 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 (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Http/Kernel.php#L111-L132

肝は $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->bootstrap();

return (new Pipeline($this->app))

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


bootstrap

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,
];

こいつらがbootstrapされる

bootstrap処理の実行コードは

foreach ($bootstrappers as $bootstrapper) {

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

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

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

みたいな感じ。イベント周りはちょいちょいいあるけど、基本的には各Bootstrapクラスの public function bootstrap() が実行される

雑な説明すると


  • \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class


    • envの読み込みとか〜



  • \Illuminate\Foundation\Bootstrap\LoadConfiguration::class


    • configの読み込みとか〜



  • \Illuminate\Foundation\Bootstrap\HandleExceptions::class



  • \Illuminate\Foundation\Bootstrap\RegisterFacades::class


    • Facade周り〜



  • \Illuminate\Foundation\Bootstrap\RegisterProviders::class


    • ServiceProvider読み込み〜



  • \Illuminate\Foundation\Bootstrap\BootProviders::class


    • AppのServciceProviderまわり〜〜




Pipeline

return (new Pipeline($this->app))

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

Pipeline 自体はLaravelでいろんなところで使われてるUtilityみたいなやつ。

登録しておいた処理を順番にやってくれるサムシングってだけ。tapとかそういうやつに近い雰囲気(適当)

dispatchToRoute が何してるかっつーと、雑にいえば「マッチするControllerのアクション見つけてそれ実行する」感じ。

つまり、「PipelineをMiddleware通しつつルートが一致するControllerのMethodを実行する」ことをやってる。

めっちゃシンプルで、これがすべて。


まとめ

意外と超シンプルなので読みやすい。


余談 Worker

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Queue/Console/WorkCommand.php

↑をおっかければいいよ。これがエントリーファイル

大事なのは

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Queue/Worker.php#L85-L128

このへん