Laravelの構成概念 第1回 ライフサイクル編

Laravelフレームワークの全体の処理の流れやサービスコンテナ、サービスプロバイダーについて調べたことをまとめます。

## シリーズ

- **Laravelの構成概念 第1回 ライフサイクル編**
- [Laravelの構成概念 第2回 サービスコンテナ編](https://qiita.com/ucan-lab/items/6f354cf6ae5e807dbada)
- [Laravelの構成概念 第3回 サービスプロバイダ編](https://qiita.com/ucan-lab/items/738abfd650be76333da6)

## 調べるきっかけ

- [Laravelのサービスコンテナをコードリーディングする会](https://connpass.com/event/205279/)

こちらのイベントに参加するにあたって、読んでおかないと話についていけないだろうなぁと思って復習がてら読んでいきます。

## 初心者の方へ

ライフサイクル・サービスコンテナ・サービスプロバイダ周りは始めたばかりの人には難易度が高いですが、Laravelの処理の流れや初期設定などの仕組みを理解できるので概要だけでも把握できると良いです。

## 環境

- Laravel 8.5.11

## 参考

- [Laravel 8.x ライフサイクル](https://readouble.com/laravel/8.x/ja/lifecycle.html)
- [Laravel 8.x サービスコンテナ](https://readouble.com/laravel/8.x/ja/container.html)
- [Laravel 8.x サービスプロバイダ](https://readouble.com/laravel/8.x/ja/providers.html)
- [Laravel 8.x ファサード](https://readouble.com/laravel/8.x/ja/facades.html)
  - (今回はあまり触れません)
- [Laravel API 8.x](https://laravel.com/api/8.x)
- [Laravel フレームワーク](https://github.com/laravel/laravel)
- [Laravel コアフレームワーク](https://github.com/laravel/framework)

Laravelの構成概念については公式ドキュメントにより詳しい内容がまとまっているので詳細を知りたい方は上記の公式ドキュメントをご覧ください。

## 用語概要

### 用語: Laravel ライフサイクル

- [Laravel 8.x ライフサイクル](https://readouble.com/laravel/8.x/ja/lifecycle.html)

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](https://github.com/laravel/laravel/blob/8.x/public/index.php)から始まります。

`php artisan foo:bar` 等のConsole経由の場合、[artisan](https://github.com/laravel/laravel/blob/8.x/artisan)から始まります。

それぞれファイルの中身を見てみましょう。
(補足のコメントを入れてます)

### public/index.php

Http経由の流れです。

```php:public/index.php
<?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経由の流れです。

```php:artisan
#!/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の補足

```php:public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
```

### bootstrap/app.php

[bootstrap/app.php](https://github.com/laravel/laravel/blob/8.x/bootstrap/app.php) ファイルの補足です。

```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;
```

まず、[bootstrap/app.php#L29-L32](https://github.com/laravel/laravel/blob/8.x/bootstrap/app.php#L29-L32)で

[Illuminate\Foundation\Application](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Application.php)のインスタンスを生成してます。

`Illuminate\Foundation\Application`は[Illuminate\Container\Container](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Container/Container.php)を継承してます。なのでサービスコンテナのインスタンスを作ってると言っても良いですね。

`Illuminate\Foundation\Application`のコンストラクタを見てみましょう。

```php: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](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Contracts/Http/Kernel.php)インターフェースをキーとして、[Illuminate/Foundation/Http/Kernel](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Http/Kernel.php)クラスを登録してます。

Consoleカーネル、例外ハンドラーも同様に登録してますね。
最後に `$app` を返して `bootstrap/app.php` は終了です。

```php:public/index.php
$kernel = $app->make(Kernel::class);
```

先ほどサービスコンテナに登録した [`Illuminate\Contracts\Http\Kernel`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Contracts/Http/Kernel.php)をキー使って、[`App\Http\Kernel`](https://github.com/laravel/laravel/blob/8.x/app/Http/Kernel.php)インスタンスを取得します。

[`App\Http\Kernel`](https://github.com/laravel/laravel/blob/8.x/app/Http/Kernel.php)のコンストラクタを見てみます。
このファイルは`vendor`配下ではなく、`app/Http/Kernel.php`と配置されてます。
また、[Illuminate\Foundation\Http](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Http/Kernel.php)を継承していて、ここのコンストラクタが実行されます。

```php:Illuminate\Foundation\Http\Kernel
    /**
     * 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](https://github.com/laravel/laravel/blob/8.x/app/Http/Kernel.php)を見るとわかります。

```php:public/index.php
$response = tap($kernel->handle(
    $request = Request::capture()
))->send();

// Laravelの終了処理
$kernel->terminate($request, $response);
```

[`$request = Request::capture()`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Http/Request.php#L60-L65)

`$_GET`, `$_POST` などを元に [`Illuminate/Http/Request`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Http/Request.php)のインスタンスを作っています。

[`$kernel->handle(...)`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Http/Kernel.php#L105-L122)
Httpカーネルのhandleメソッドを実行して[`Illuminate/Http/Request`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Http/Request.php)インスタンスを渡して実行してます。
ルーティングやコントローラを通って、最終的に[`Illuminate\Http\Response`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Http/Response.php)インスタンスを生成して返します。

[`$response = tap(...)->send()`](https://github.com/symfony/http-foundation/blob/5.x/Response.php#L391-L403)

`Illuminate\Http\Response` の `send` メソッドを実行してます。
`Illuminate\Http\Response` は `Symfony\Component\HttpFoundation` を継承していて、Symfonyコンポーネントのメソッドを呼び出してます。
ここでは、Httpレスポンスヘッダーやレスポンスボディの出力を行ってます。
つまりはここで最終的なHTMLが出力されてる訳ですね。

`tap`ヘルパーメソッドを使うと、メソッドチェーンで関数を実行しても、返り値はtap関数の第一引数に渡した変数が返ってきます。
つまりは、 `Illuminate\Http\Response` のインスタンスが `$response` に入ります。

- [Tap, Tap, Tap](https://medium.com/@taylorotwell/tap-tap-tap-1fc6fc1f93a6)
- [【Laravel】知っていると便利なヘルパー関数！tap()の使い方を紹介します](https://public-constructor.com/laravel-about-tap-function/)

最後にLaravelの終了部分です。

[`$kernel->terminate($request, $response);`](https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Http/Kernel.php#L177-L182)

```php:Illuminate\Foundation\Http\Kernel
    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`メソッドがあればそれを実行して終了処理を行っていきます。

## コントローラのアクション実行まで

この記事は簡単な概要でしたが、こちらの記事でコントローラのアクション実行まで丁寧に書かれている記事があったのでご紹介します。

https://zenn.dev/shlia/scraps/bb85c4eaeb7f02

## さいごに

今回はLaravel ライフサイクルについて詳しく見ていきました。
改めて調べるとまだまだ知らない部分が発見できてよかったです。

サービスコンテナやサービスプロバイダについてもまとめて紹介しようと思いましたが、
流石に長くなるので別記事に分けて紹介しようと思います。