177
180

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-02-27

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

シリーズ

調べるきっかけ

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

初心者の方へ

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

環境

  • Laravel 8.5.11

参考

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経由の流れです。

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経由の流れです。

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の補足

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

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;

まず、bootstrap/app.php#L29-L32

Illuminate\Foundation\Applicationのインスタンスを生成してます。

Illuminate\Foundation\ApplicationIlluminate\Container\Containerを継承してます。なのでサービスコンテナのインスタンスを作ってると言っても良いですね。

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

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 は終了です。

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

先ほどサービスコンテナに登録した Illuminate\Contracts\Http\Kernelをキー使って、App\Http\Kernelインスタンスを取得します。

App\Http\Kernelのコンストラクタを見てみます。
このファイルはvendor配下ではなく、app/Http/Kernel.phpと配置されてます。
また、Illuminate\Foundation\Httpを継承していて、ここのコンストラクタが実行されます。

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を見るとわかります。

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

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

$request = Request::capture()

$_GET, $_POST などを元に Illuminate/Http/Requestのインスタンスを作っています。

$kernel->handle(...)
Httpカーネルのhandleメソッドを実行してIlluminate/Http/Requestインスタンスを渡して実行してます。
ルーティングやコントローラを通って、最終的にIlluminate\Http\Responseインスタンスを生成して返します。

$response = tap(...)->send()

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

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

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

$kernel->terminate($request, $response);

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

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

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

さいごに

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

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

177
180
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
177
180

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?