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__.'/../')
);
これがLaravelの全ての起点だよ!!!
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);
}
}
}
割と大事なのが class Application extends Container implements ApplicationContract, HttpKernelInterface
のとこで、
Application
は Container
を継承しているやね
つまり、 Application
自信がコンテナとして動くので、 app(XxxClass::class)
みたいにしたときに、 Application
がいろんな解決をすることができるんだね。すごいね。
細いことはいろいろあるけど、基本的にはこの段階で「Laravelの起動に最低限必要なクラス群を読み込めるようにする準備」として「Application自身にいろんなものを設定する」ことをしているよ。
その他↑この辺はきっと何か似たような登録処理をしているイメージ。
このファイルはフレームワーク側じゃないので、何かいろいろ初期化処理したかったらここに書くのはワンチャンあるよ。
ほぼほぼほぼほぼのケースでは、ちゃんと書くべき場所が別にあるからレアケース。
$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);
}
}
HTTPのKernelにおいては、 Middlewareの登録処理が行われてることがわかりますね
どういうMiddlewareが登録されているかはApplication側で定義されている感じ
Handle Request
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
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;
}
肝は $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
↑をおっかければいいよ。これがエントリーファイル
大事なのは
このへん