Laravelフレームワークの全体の処理の流れやサービスコンテナ、サービスプロバイダーについて調べたことをまとめます。
シリーズ
- Laravelの構成概念 第1回 ライフサイクル編
- Laravelの構成概念 第2回 サービスコンテナ編
- Laravelの構成概念 第3回 サービスプロバイダ編
調べるきっかけ
こちらのイベントに参加するにあたって、読んでおかないと話についていけないだろうなぁと思って復習がてら読んでいきます。
初心者の方へ
ライフサイクル・サービスコンテナ・サービスプロバイダ周りは始めたばかりの人には難易度が高いですが、Laravelの処理の流れや初期設定などの仕組みを理解できるので概要だけでも把握できると良いです。
環境
- Laravel 8.5.11
参考
- Laravel 8.x ライフサイクル
- Laravel 8.x サービスコンテナ
- Laravel 8.x サービスプロバイダ
-
Laravel 8.x ファサード
- (今回はあまり触れません)
- Laravel API 8.x
- Laravel フレームワーク
- Laravel コアフレームワーク
Laravelの構成概念については公式ドキュメントにより詳しい内容がまとまっているので詳細を知りたい方は上記の公式ドキュメントをご覧ください。
用語概要
用語: Laravel ライフサイクル
Laravelが起動して終了するまでのLaravel全体の処理の流れをライフサイクルといいます。
ちなみに入り口はHttpとConsoleの二つあります。
用語: Laravel サービスコンテナ
クラスの依存関係を管理し、依存注入(DI)を実行するための機能です。
用語: Laravel サービスプロバイダ
Laravelアプリケーション全体の起動処理における初期起動処理を行っています。
サービスプロバイダからサービスコンテナへDIの設定(コンテナ結合)を行います。
サービスプロバイダーでは、コンテナ結合以外にもイベントリスナ、フィルター、ルーティングの設定なども行います。
用語: DI(Dependency Injection)とは
Dependency Injectionは依存性の注入となります。
PHPを実行する際に抽象クラス(interface)を具象クラスに差し替えて実行できる。
テストの時は、DBやAPIを実際にアクセスさせないために差し替えたり等テストコードが書きやすくなるメリットがあります。
本題: Laravelライフサイクル
http://localhost/foo/bar
等のHttp経由の場合、public/index.phpから始まります。
php artisan foo:bar
等のConsole経由の場合、artisanから始まります。
それぞれファイルの中身を見てみましょう。
(補足のコメントを入れてます)
public/index.php
Http経由の流れです。
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
// タイマー開始。フレームワークの起動にかかる時間などを測れる
define('LARAVEL_START', microtime(true));
// メンテナンスモードのファイルがあればメンテナンス画面を表示して終了する
// https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Console/stubs/maintenance-mode.stub
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
require __DIR__.'/../storage/framework/maintenance.php';
}
// composer dump-autoload で生成されたオートローダーを読み込む
// オートローダーとは外部にあるPHPファイルを自動的に読み込む仕組みです
// https://genkiroid.github.io/2016/07/15/about-composer-autoload
require __DIR__.'/../vendor/autoload.php';
// ここからLaravelのコアシステムが始まります
// Illuminate\Foundation\Application のインスタンスを取得して $app に格納してます
// サービスコンテナを生成してHttpカーネル、Consoleカーネル、例外ハンドラーのインスタンスの生成を行います
// https://github.com/laravel/laravel/blob/8.x/bootstrap/app.php
$app = require_once __DIR__.'/../bootstrap/app.php';
// 上の行で生成したHttpカーネルのインスタンスを取得してます(App\Http\Kernel)
$kernel = $app->make(Kernel::class);
// tapやsendの挙動がどうなっているのか怪しいですが...
// 送信されてきたHttpリクエスト(GET, POSTの中身)をHttpカーネルへ渡してHttpレスポンスを取得してます
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
// Laravelの終了処理
$kernel->terminate($request, $response);
artisan
Console経由の流れです。
#!/usr/bin/env php
<?php
// タイマー開始。Httpの時と同じ
define('LARAVEL_START', microtime(true));
// オートローダーの読み込み。Httpの時と同じ
require __DIR__.'/vendor/autoload.php';
// Illuminate\Foundation\Application インスタンスの取得。Httpの時と同じ
$app = require_once __DIR__.'/bootstrap/app.php';
// サービスコンテナからコンソールカーネルのインスタンス(App\Console\Kernel)を取得している
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
// 引数入力インスタンスとコンソール出力インスタンスをコンソールカーネルへ渡して実行する
// 実行結果のステータスを受け取る
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
// Laravelの終了処理
$kernel->terminate($input, $status);
// $statusを出力して、スクリプトを終了する
// https://www.php.net/manual/ja/function.exit.php
exit($status);
public/index.phpの補足
$app = require_once __DIR__.'/../bootstrap/app.php';
bootstrap/app.php
bootstrap/app.php ファイルの補足です。
<?php
// 新しいLaravelアプリケーションのインスタンスを生成してます
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// インターフェースに具象クラスをサービスコンテナにバインドしてます
// Httpカーネルのバインド
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// Consoleカーネルのバインド
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
// 例外ハンドラーのバインド
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
Illuminate\Foundation\Applicationのインスタンスを生成してます。
Illuminate\Foundation\Application
はIlluminate\Container\Containerを継承してます。なのでサービスコンテナのインスタンスを作ってると言っても良いですね。
Illuminate\Foundation\Application
のコンストラクタを見てみましょう。
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
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->singleton(Mix::class);
$this->singleton(PackageManifest::class, function () {
return 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' => [self::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],
// ... 略
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
}
この段階ではLaravelを起動するための最低限のクラスが読み込まれています。
深掘りしていくとキリがないのでここは気になったら詳しくみていけば良いかなと思います。
続いて、
Illuminate\Contracts\Http\Kernelインターフェースをキーとして、Illuminate/Foundation/Http/Kernelクラスを登録してます。
Consoleカーネル、例外ハンドラーも同様に登録してますね。
最後に $app
を返して bootstrap/app.php
は終了です。
$kernel = $app->make(Kernel::class);
先ほどサービスコンテナに登録した Illuminate\Contracts\Http\Kernel
をキー使って、App\Http\Kernel
インスタンスを取得します。
App\Http\Kernel
のコンストラクタを見てみます。
このファイルはvendor
配下ではなく、app/Http/Kernel.php
と配置されてます。
また、Illuminate\Foundation\Httpを継承していて、ここのコンストラクタが実行されます。
/**
* Create a new HTTP kernel instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
$this->syncMiddlewareToRouter();
}
protected function syncMiddlewareToRouter()
{
$this->router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$this->router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$this->router->aliasMiddleware($key, $middleware);
}
}
Httpカーネルクラスの初期処理でミドルウェアの設定が行われてますね。
どんなミドルウェアが登録されているかはApp\Http\Kernelを見るとわかります。
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
// Laravelの終了処理
$kernel->terminate($request, $response);
$_GET
, $_POST
などを元に Illuminate/Http/Request
のインスタンスを作っています。
$kernel->handle(...)
Httpカーネルのhandleメソッドを実行してIlluminate/Http/Request
インスタンスを渡して実行してます。
ルーティングやコントローラを通って、最終的にIlluminate\Http\Response
インスタンスを生成して返します。
Illuminate\Http\Response
の send
メソッドを実行してます。
Illuminate\Http\Response
は Symfony\Component\HttpFoundation
を継承していて、Symfonyコンポーネントのメソッドを呼び出してます。
ここでは、Httpレスポンスヘッダーやレスポンスボディの出力を行ってます。
つまりはここで最終的なHTMLが出力されてる訳ですね。
tap
ヘルパーメソッドを使うと、メソッドチェーンで関数を実行しても、返り値はtap関数の第一引数に渡した変数が返ってきます。
つまりは、 Illuminate\Http\Response
のインスタンスが $response
に入ります。
最後にLaravelの終了部分です。
$kernel->terminate($request, $response);
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);
$this->app->terminate();
}
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
[$name] = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}
Laravelの終了時にもミドルウェアを差し込めるようですね。
各種ミドルウェアにterminate
メソッドがあればそれを実行して終了処理を行っていきます。
コントローラのアクション実行まで
この記事は簡単な概要でしたが、こちらの記事でコントローラのアクション実行まで丁寧に書かれている記事があったのでご紹介します。
さいごに
今回はLaravel ライフサイクルについて詳しく見ていきました。
改めて調べるとまだまだ知らない部分が発見できてよかったです。
サービスコンテナやサービスプロバイダについてもまとめて紹介しようと思いましたが、
流石に長くなるので別記事に分けて紹介しようと思います。