Home > アーカイブ > 2014-03

2014-03

Laravel コードで見るファサードクラスの仕組み

この記事の所要時間: 839

Laravel の特徴として良く挙げられるファサードクラスの仕組みをコードで見てみました。

laravel

Laravel のファサードクラス

Laravel では、Input::get()Route::get()など、クラスメソッドでフレームワークの機能を利用する場面が多くあります。

これは一見すると、InputやRouteクラスで提供されているクラスメソッドを実行しているだけに見えますが、これらのクラスにメソッドの実装があるわけではなく、実際はIoCコンテナに格納されているインスタンスのメソッドを実行しています。

例えば、InputであればIlluminate\Http\Requestクラス、RouteであればIlluminate\Routing\Routerのインスタンスメソッドが実行されます。

これらのインスタンスは、IoCコンテナにて管理されており、ファサードクラスのクラスメソッドが実行されると、IoCコンテナから定められたインスタンスを取得して、そのインスタンスメソッドを実行する仕組みになっています。

ファサードクラスの利用により、コードの記述がシンプルになります(ここは意見が分かれるところですが)。また、ファサードクラスでのメソッド呼び出しでは、自身では処理を行わずに、IoCコンテナに格納されたインスタンスなどに委譲します。つまり、ファサードクラスが呼び出すインスタンスを差し替えることで実際に処理を行うクラスを変えることができます。

Laravel のファサードクラスでは、shouldReceive()メソッドにて、テスト時に実行するインスタンスをモックオブジェクトと差し替える機能が標準で用意されています。

ファサードクラス実行の流れ

ではRoute::get()を例にして、ファサードクラスがどのように実行されるか見て行きましょう。

  • 1) ファサードクラスの解決
  • 2) ファサードクラスのクラスメソッド実行
  • 3) IoCコンテナからインスタンス取得
  • 4) インスタンスメソッドの実行

1. ファサードクラスの解決

まずRouteクラスの解決を行います。実は、Routeクラスは、フレームワークでは定義されていません。

では、なぜRouteクラスのクラスメソッドが実行できるのかというと、class_alias()関数にて、存在するクラスの別名として、Routeを定義するためです。

この処理はIlluminate\Foundation\AliasLoaderにて行います。

Routeクラスへのアクセスがあると、クラス定義を探すためにオートローダが起動します。オートローダには、Illuminate\Foundation\AliasLoaderとComposerのものが登録されているのですが、はじめにIlluminate\Foundation\AliasLoaderが実行されます。

Illuminate\Foundation\AliasLoaderでオートロードを行う箇所が以下です。

$this->aliasesに対象のクラス名(ここではRoute)があれば、class_alias()で、クラス別名を設定します。

	public function load($alias)
	{
		if (isset($this->aliases[$alias]))
		{
			return class_alias($this->aliases[$alias], $alias);
		}
	}

$this->aliasesは、app/config/app.phpで定義しているaliasesキーの内容がセットされています。

Routeは、下記のように設定されているため、Illuminate\Support\Facades\Routeへの別名として設定されます。

		'Route'           => 'IlluminateSupportFacadesRoute',

次にIlluminate\Support\Facades\Routeについて、オートロードによる読み込みが行われるので、RouteIlluminate\Support\Facades\Routeの別名として利用できるようになります。

2. Routeクラスのクラスメソッドを実行

次に、Routeクラスのget()メソッドを実行します。

Routeクラス(Illuminate\Support\Facades\Route)を見ると、get()というメソッドは存在しません。

Illuminate\Support\Facades\Routeの基底クラスであるIlluminate\Support\Facade\Facadeには、__callStatic()メソッドが定義されているため、このメソッドが呼ばれます。

__callStatic()メソッドが下記です。

	public static function __callStatic($method, $args)
	{
		$instance = static::resolveFacadeInstance(static::getFacadeAccessor());

		switch (count($args))
		{
			case 0:
				return $instance->$method();

			case 1:
				return $instance->$method($args[0]);

			case 2:
				return $instance->$method($args[0], $args[1]);

			case 3:
				return $instance->$method($args[0], $args[1], $args[2]);

			case 4:
				return $instance->$method($args[0], $args[1], $args[2], $args[3]);

			default:
				return call_user_func_array(array($instance, $method), $args);
		}
	}

}

3. IoC コンテナからインスタンス取得

__callStatic()メソッドの先頭では、resolveFacadeInstance()メソッドを実行して、IoC コンテナから処理対象のインスタンスを取得します。

まず、static::getFacadeAccessor()を実行して、どのインスタンスを取得するかを決定します。このメソッドは各ファサードクラスで定義されており、Routeクラスの場合は下記のようになっています。ここでは、routerという文字列を返しています。

このようにファサードクラスではgetFacadeAccessor()の戻り値で、実行するインスタンスを指定します。

	protected static function getFacadeAccessor() { return 'router'; }

このrouterを引数にresolveFacadeInstance()メソッドを実行して、インスタンスを取得します。

resolveFacadeInstance()メソッドの実装は以下です。

	protected static function resolveFacadeInstance($name)
	{
		if (is_object($name)) return $name;

		if (isset(static::$resolvedInstance[$name]))
		{
			return static::$resolvedInstance[$name];
		}

		return static::$resolvedInstance[$name] = static::$app[$name];
	}

引数で与えられた$name(ここではrouter)がオブジェクトであれば、そのまま返します。もし、すでにファサードクラスで解決済ならそのインスタンスを返します。そうでなければ、IoCコンテナからインスタンスを取得して返します。

IoCコンテナからインスタンスを取得した場合は、Fasadeクラスのクラス定数$resolvedInstanceにキャッシュされる仕組みになっているので、常に同じインスタンスが利用されます。

もし実行インスタンスを変えたい場合はswap()clearResolvedInstance()を使うと良いでしょう。

4. インスタンスメソッドの実行

  1. で取得したインスタンスについて、指定されたインスタンスメソッド(この場合get())を実行します。

さいごに

フレームワークのソースから Laravel のファサードクラスの仕組みを見てきました。

ファサードクラスの作り方として、ServiceProvider の構築が良く手順に含まれていますが、実はファサードクラスを作る上ではこれは必須ではありません。ファサードクラスが IoC コンテナからインスタンスを取得するため、そのインスタンスを事前にコンテナに設定するために ServiceProvider を利用することが多いだけです。

ファサードクラスを自作する際もこうした動きを知っていると作りやすいですね。

なお、Laravel のファサードクラスは、GoF のファサードパターンとは異なり、IoCコンテナにあるインスタンスを透過的に呼び出す仕組みとなっており、便利なサービスロケータと言った方がイメージしやすいかもしれません。( ちなみに、Laravel のファサードと GoF のファサードパターンは違うものだそうです。)

  • コメント (Close): 0
  • トラックバック (Close): 0

Vagrant 1.5 で追加された Rsync Synced Folder が良い

この記事の所要時間: 1025

Vagrant 1.5 がリリースされました。1.1 以来の big change ということで、目新しい機能が追加されています。

vagrant

目玉はやはり Vagrant Share だと思うのですが、その他にも Web システムの開発に Vagrant を使っている人には嬉しい機能が追加されています。

その一つが Rsync Synced Folder です。

Rsync Synced Folder

Vagrant には、ホストマシンとゲストマシンとでファイルを共有、同期する機能があります。

これにより、開発はホストマシンで行い、実行は LA(MP)P 環境であるゲストマシンで行うという、いいとこ取りができました。

この機能を実現する方法として、shared folder(VirtualBox の機能)や NFS を利用することができたのですが、これに rsync による同期が追加されました。

ホストマシンからゲストマシンへ rsync で同期を行います。

嬉しいこと

Rsync Synced Folder は、rsyncで同期を行うだけなので、ゲストマシン側はネイティブなファイルシステムが利用できます。つまり、synced folder の仕様に影響されなくなります。

パフォーマンス

分かりやすいメリットとしては、VirtualBox の shared folder に比べると、パフォーマンスが向上します。手元にある PHP アプリケーションでは、shared folder で 90 秒程度かかっていたユニットテストが、60 秒程度に短縮しました。

また、NFS と違って、ホスト側でデーモンを起動しておく必要が無いのも大きなメリットです。

パーミッション

shared folder では、パーミッションをディレクトリやファイルごとに設定することができなかったのですが、rsync を利用すれば、ゲストマシンからは通常のファイルシステムなので何も支障がありません。

ただ、現状の Rsync Synced Folder では、同期時にファイルオーナーを強制的に書き換える仕様になっているので、この点については留意しておく必要があります。(後述)

自動更新機能

この rsync は、基本的にはvagrant up, vagrant reload, vagrant provision、そして今回新設されたvagrant rsyncコマンド実行時に行われます。

これだとファイルを変更する度にコマンドを実行するのが面倒になるのですが、vagrant rsync-autoコマンドを実行することで、自動で同期を行うことができます。設定で同期対象となったディレクトリに変更があれば、自動で同期処理を行います。

どのようなプロバイダでも同期可能

AWS や DigitalOcean のようなクラウドサービスをプロバイダとして使っている場合も、rsync-auto による自動同期が可能です。

コードを変更すると、クラウド上のサーバに自動で同期されます。

要件

この機能を利用するには、ホストとゲストの双方にrsyncコマンドが必要になります。

ホストについては、OSX であればデフォルトでインストールされています。

ゲストについては、もしインストールされていなくても、主要な OS については、Vagrant が自動でインストールしてくれます。例えば、RedHat 系であれば yum -y install rsyncが実行されます。

これは気が効いてますね。

設定

Vagrantfile の synced folder の設定で、type=rsyncを指定します。

config.vm.synced_folder "src", "/share", type: "rsync"

設定には下記のようなオプションがあります。

  • rsync__args
    rsync コマンドに渡す引数です。デフォルトでは["--verbose", "--archive", "--delete", "-z"]が指定されています。

  • rsync__auto
    rsync-auto による自動同期の対象にするかどうかです。デフォルトは、trueです。

  • rsync__exclude
    同期の対象外にするファイルやディレクトリを指定します。デフォルトでは[".vagrant/"]が指定されています。

例えば、.git ディレクトリは同期対象から外すなら、下記のようにrsync__excludeを指定します。

config.vm.synced_folder "src", "/share", type: "rsync", rsync__exclude: [".git/"]

手動で同期する

vagrant upvagrant reloadvagrant provisionvagrant rsyncを実行すると、Vagrantfile の synced_folder で指定したディレクトリについて、rsync による同期を行います。

ファイルを同期したいだけなら、vagrant rsyncを実行します。

$ vagran rsync
==> default: Rsyncing folder: /path/to/src/ => /share

自動で同期する

自動で同期するには、vagrant rsync-autoコマンドを実行します。コマンドを実行すると、synced_folder で指定した同期対象のディレクトリを監視します。

$ vagrant rsync-auto
==> default: Watching: /path/to/src/

同期対象のディレクトリに変更があると、自動でrsyncが実行され、同期されます。

$ vagrant rsync-auto
==> default: Watching: /path/to/src/
==> default: Rsyncing folder: /path/to/src/ => /share

ホストで開発して、ゲストで実行するというスタイルなら、常に rsync-auto を実行しておくと良いでしょう。

注意点

ホストからゲストの同期のみ

現在のところ、同期はホストからゲストの一方向のみです。

ゲストからホストへファイルを転送するvagrant rsync --pullというのが、issue で提案されているので、いずれ実装されるかもしれません。

https://github.com/mitchellh/vagrant/issues/3062

rsync 実行でファイルオーナーが変更される

rsync-auto を使っている時に気づいたのですが、rsyncの実行前にゲスト側の同期ディレクトリに対してchown -R vagrant /pathを実行して、ファイルオーナーを変更しています。

同期対象ディレクトリを丸ごとホスト側と同期するなら特に問題無いのですが、ゲストでアプリケーションが実行時に生成するファイル(ログファイル等)をrsync__excludeオプションで同期対象外にしている時に問題が発生します。

例えば、アプリケーションがapp/storage/log/以下にログファイルを出力する場合、このディレクトリを同期対象外とします。

config.vm.synced_folder "src", "/share", type: "rsync", rsync__exclude: [".git/", "app/storage/log/*"]

この時、アプリケーションを実行するとゲストでは、下記のようにアプリケーションの実行ユーザである apache ユーザによってログファイル(laravel.log)が生成されます。

[vagrant@localhost /share]$ ls -la app/storage/log/
drwxrwxrwx. 2 vagrant vagrant     4096  3月 13 05:51 2014 .
drwxrwxrwx. 7 vagrant vagrant     4096  2月 13 07:50 2014 ..
-rw-rw-rw-. 1 vagrant vagrant       13  2月 13 07:50 2014 .gitignore
-rw-r--r--. 1 apache  apache      5394  3月 13 05:51 2014 laravel.log

次にホスト側で vagrant rsyncで同期します。

$ vagrant rsync
==> default: Rsyncing folder: /path/to/src/ => /share

ゲスト側では laravel.log は、同期対象外となっているのでファイルはそのまま残っています。しかし、ファイルオーナーが vagrant に変更されています。

[vagrant@localhost /share]$ ls -la app/storage/log/
drwxrwxrwx. 2 vagrant vagrant     4096  3月 13 05:51 2014 .
drwxrwxrwx. 7 vagrant vagrant     4096  2月 13 07:50 2014 ..
-rw-rw-rw-. 1 vagrant vagrant       13  2月 13 07:50 2014 .gitignore
-rw-r--r--. 1 vagrant apache      5394  3月 13 05:51 2014 laravel.log

この状態でアプリケーションを実行すると、ログファイルへの書き込み権限が無いためにエラーが発生します。

Vagrant のソースを見ると、plugins/guests/linux/cap/rsync.rb で、rsync 前に実行する処理が定義されているのですが、たしかにsudo chown -Rが実行されています。

        def self.rsync_pre(machine, folder_opts)
          username = machine.ssh_info[:username]

          machine.communicate.tap do |comm|
            comm.sudo("mkdir -p '#{folder_opts[:guestpath]}'")
            comm.sudo("chown -R #{username} '#{folder_opts[:guestpath]}'")
          end
        end

おそらく、vagrant rsync を実行する際に、Permission Denied が出ないように、一括でchownしているのだと思うのですが、ここは注意しておく必要があります。

rsync-auto を忘れる

これはケアレスミスなのですが、VirtualBox の shared folder や NFS であれば、自動で同期するので、ホストでコードを変更したのに、ゲストに反映されずに「あれ?」となったことが何度かありました。

vagrant rsync-autoを実行しないと、自動で同期されないので、忘れずに。

さいごに

Rsync Synced Folder を使うことで、shared folder で気になっていた点が解決しそうです。なんといっても、ユニットテストが速くなるのはありがたいですね。

  • コメント (Close): 0
  • トラックバック (Close): 0

composer install をどこで実行するか

  • 2014-03-06 (木)
  • PHP
この記事の所要時間: 32

最近の PHPer が集まれば、一度は話題に上がるのが、この composer install をどこで実行するのか問題。

Composer

これまで聞いた話をまとめると、大きく分けて、以下の2パターンになります。どちらの方法を取っているか教えて下さい 😀

0. 前提

前提ですが、以下のような方法で、Composer 関連のファイルは管理しているとします。おそらく多くはこのような形になっていると思います。

  • PHP コードは、Git などの VCS で管理する。
  • composer.json, composer.lock は、VCS で管理する。
  • composer.phar, vendor/ は、VCS で管理しない。

また、今回対象としているのはアプリケーションで、Packagist に登録して、配布するようなフレームワークやライブラリは対象外です。

1. 本番サーバで実行

本番PHPサーバ上で composer install を実行するパターンです。

本番PHPサーバに何かしらの方法で、PHP コードと共にcomposer.jsoncomposer.lockを設置し、composer installを実行します。

複数台サーバがある場合、それぞれのサーバで実行します。

コマンドの実行は、SSH でログインして手でする人もいるでしょうし、CapistranoやFabricなどで、自動化している人もいます。

順番としては、PHPコードをデプロイしてから、依存解決(composer install)になります。

2. 別サーバで実行して、実行結果を本番サーバに設置

本番PHPサーバ以外の場所で、composer installを実行して、生成されたvendor/を本番サーバに設置する方法です。

composer installを実行する場所は、ビルドサーバの場合もありますし、開発者の PC の場合もあります。

本番サーバではcomposer installを実行しないのがポイントです。

こちらは、依存解決してからデプロイの順になります。

私の場合

私自身は、本番環境については、2. の方法で行っています。

依存解決は、デプロイの前に行っておき、すでに依存解決されたものを本番環境にデプロイします。

The Twelve-Factor App で言うところのビルドステージで行うものという認識です。

ビルドステージ は、コードリポジトリを ビルド と呼ばれる実行可能な塊へと変える変換である。デプロイプロセスで指定したコミットのコードで指定されたバージョンを使って、ビルドステージは依存関係を取得してローカル環境に配置し、バイナリやアセットファイルをコンパイルする。

ビルドステージで可変部分は全て処理しておき、本番環境には、イミュータブルなパッケージとしてデプロイします。(実際は、パーミッションの設定など多少の操作は必要ですが)

複数台の場合、あちこちでcomposer installを実行するのが単純に無駄というのもありますし、大きな可変部分を各サーバで実行するより、1箇所で生成して、全く同じものを各サーバに配布した方が安全かと。

ただ、1. の方が手軽なので、こちらを採用しているという意見も良く聞きますね。

さいごに

Composer が普及してきたからこそ出てくる議論だと思うので、すっかり利用するのが当たり前になりましたね。今後は、こうした運用面に関するノウハウも共有していきたいです。

上記案に限らず、こうしてるよ、などあれば教えて下さい。

  • コメント (Close): 0
  • トラックバック (Close): 0

Laravel コードからフレームワークの起動から終了までの流れを追う

この記事の所要時間: 1633

Laravel フレームワークが起動してから終了するまでの流れについて、コードを読んでみました。

laravel_code

今回読んだフレームワークのバージョンは、4.1.21 です。

エントリポイント

エントリポイントは、public/index.php です。

このファイルではコメントが多数ありますが、実際に処理を行っている行は、下記の 3 行だけです。

このコードから想像できるように、オートローダの設定、フレームワークの設定、そして実行という流れです。

require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/start.php';
$app->run();

それぞれについて見ていきます。

1. bootstrap/autoload.php

ここでは、オートローダの設定を行います。Composerのオートローダもここで読み込みます。

先頭で現在時刻が定数として定義されているので、$_SERVER['REQUEST_TIME']の代わりに使えるかもしれません。

define('LARAVEL_START', microtime(true));

2. bootstrap/start.php

次にフレームワークとアプリケーションの起動を行います。

フレームワークの中核を成すIlluminate\Foundation\Applicationクラスのインスタンスを生成して、実行環境の設定、ファイルパスの設定を行って、フレームワークの起動ファイルを読み込みます。

最後に、Illuminate\Foundation\Applicationクラスのインスタンスを返します。

$app = new IlluminateFoundationApplication;

$env = $app->detectEnvironment(function() {
    return getenv('ENV_STAGE') ?: 'local';
});

$app->bindInstallPaths(require __DIR__.'/paths.php');

$framework = $app['path.base'].'/vendor/laravel/framework/src';
require $framework.'/Illuminate/Foundation/start.php';

return $app;

下記は、require文で読み込むファイルについてです。

2-1. bootstrap/paths.php

アプリケーション関連のパスが連想配列で定義されています。必要があれば、このファイルを編集します。

2-2. vendor/laravel/framework/src/Illuminate/Foundation/start.php

フレームワークの起動処理を行います。

Laravel をはじめる際につまづくことの多いmcrypt拡張チェックはここで行われています。

  • error_reporting(-1)
  • mcrypt 拡張チェック
  • IoC コンテナに$appを入れる
  • ユニットテストなら、$app['env']に’testing`をセット
  • ファサードクラスの初期処理
  • コアクラスを短縮名で呼べるように IoC コンテナに別名をセット
    => Application#registerCoreContainerAliases()
  • 環境変数をセット
    => .env.php or .env.${enviroment}.php の値を $_ENV / $_SERVER / putenv() にセット
  • Configクラス(Illuminate\Config\Repository)を$app['config']にセット
  • 例外ハンドリングの開始
  • display_errors を Off にする
  • タイムゾーン設定(date_default_timezone_set()
  • AliasLoader()の設定
  • HTTP メソッドのオーバーライド有効化
  • コアのServiceProvider有効化
  • bootedハンドラの登録(アプリケーション起動時に実行)
    => start/global.php の読み込み
    => start/{$env}.php の読み込み
    => routes.php の読み込み

3. $app->run()

アプリケーションを実行します。run()メソッドは、わずか 4 行です。

まず、Symfony\Component\HttpFoundation\Requestクラスのインスタンスを取得します。index.phpでは、引数をセットしていないので、IoCコンテナからインスタンスを取得します。

次に、実行するミドルウェアのツリーを構築して、handle()メソッドでアプリケーションの実行を行います。この行がアプリケーション実行の中心となります。

Symfony\Component\HttpFoundation\Responseクラスのインスタンスが戻り値として返るので、send()メソッドでレスポンスを出力します。

そして、最後に先ほど構築したミドルウェアツリーのterminate()メソッドを実行して、終了処理を行います。

これでアプリケーションが終了します。

	public function run(SymfonyRequest $request = null)
	{
		$request = $request ?: $this['request'];

		$response = with($stack = $this->getStackedClient())->handle($request); // (1)

		$response->send();

		$stack->terminate($request, $response);
	}

下記では、(1) の行について見ていきます。

3-1. $this->getStackedClient()

getStackedClient()メソッドでは、Stack/Builder を使って、実行するミドルウェアを組み合わせていきます。

ミドルウェアは、Illuminate\Foundation\Applicationクラス(正確には、`Symfony\Component\HttpKernel\HttpKernelInterface を implement したクラス)の Decorator となっており、それぞれの処理をアプリケーション実行の前後に挟むことができます。

デフォルトでは、以下の 4 クラスがミドルウェアとして登録されます。(handle()メソッド呼び出し順)

  • Illuminate\Http\FrameGuard
  • Illuminate\Session\Middleware
  • Illuminate\Cookie\Queue
  • Illuminate\Cookie\Guard

ミドルウェアはApp::middleware()で任意のものを追加することもできます。ただ、追加する場合は、Application#run()が呼ばれる前に追加しておく必要があります。

	protected function getStackedClient()
	{
		$sessionReject = $this->bound('session.reject') ? $this['session.reject'] : null;

		$client = with(new StackBuilder)
						->push('IlluminateCookieGuard', $this['encrypter'])
						->push('IlluminateCookieQueue', $this['cookie'])
						->push('IlluminateSessionMiddleware', $this['session'], $sessionReject);

		$this->mergeCustomMiddlewares($client);

		return $client->resolve($this);
	}

3-2. Illuminate\Cookie\Guard

直接Illuminate\Foundation\Applicationクラスのhandle()メソッドを呼び出すミドルウェアがこのクラスです。

実行するhandle()メソッドは、下記です。

	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		return $this->encrypt($this->app->handle($this->decrypt($request), $type, $catch));
	}

1行で一気に書いているので、個々の文をばらしてみます。

	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		$request = $this->decrypt($request);
		$response = $this->app->handle($request, $type, $catch);
		$response = $this->encrypt($response);		
		return $response;
	}

まず、decrypt()メソッドでリクエストされたクッキーの複合を行います。

次に、自身が保持しているミドルウェアのhandle()メソッドを実行します。ここで保持しているミドルウェアは、Applicationクラスなので、アプリケーションが実行されます。

そして、最後にレスポンスのクッキーを暗号化して、レスポンスを返します。

つまり、アプリケーションが実行している間は、クッキーの内容は平文ですが、その前後で複合、暗号化を行っているので、この範囲外では、クッキーの内容は暗号化されている状態になります。

3-3. Illuminate\Foundation\Application#handle()

前途のとおり、Illuminate\Cookie\Guard#handle()から呼び出されます。

boot()メソッドでアプリケーションの起動処理を行い、dispatch()メソッドでアプリケーションを実行します。

この処理で、もし例外が発生した場合、IoC コンテナにある例外ハンドラで処理します。ユニットテスト中であれば、そのままスローします。

	public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		try
		{
			$this->refreshRequest($request = Request::createFromBase($request));

			$this->boot();

			return $this->dispatch($request);
		}
		catch (Exception $e)
		{
			if ($this->runningUnitTests()) throw $e;

			return $this['exception']->handleException($e);
		}
	}

3-4. Illuminate\Foundation\Application#boot()

Illuminate\Foundation\Application#handle()から呼び出されます。

アプリケーションのServiceProviderのboot()メソッドを実行していきます。

また、2-2. で登録したbootedハンドラは、ここから呼ばれるbootApplication()メソッドで実行されます。

	public function boot()
	{
		if ($this->booted) return;

		array_walk($this->serviceProviders, function($p) { $p->boot(); });

		$this->bootApplication();
	}

3-5. Illuminate\Foundation\Application#dispatch()

Illuminate\Foundation\Application#handle()から呼び出されます。

まず、メンテナンス中かを確認して、もしそうならilluminate.app.downイベントを発火してレスポンスを返します。

次に、ユニットテスト中かつセッション開始前なら、セッションを開始します。

そして、Illuminate\Routing\Routerクラスのdispatch()メソッドを実行します。

	public function dispatch(Request $request)
	{
		if ($this->isDownForMaintenance())
		{
			$response = $this['events']->until('illuminate.app.down');

			if ( ! is_null($response)) return $this->prepareResponse($response, $request);
		}

		if ($this->runningUnitTests() && ! $this['session']->isStarted())
		{
			$this['session']->start();
		}

		return $this['router']->dispatch($this->prepareRequest($request));
	}

3-6. Illuminate\Routing\Router#dispatch()

Illuminate\Foundation\Application#dispatch()から呼び出されます。

ここで、リクエストをアプリケーションに渡して実行します。dispatchRoute()メソッドがアプリケーション実行のメインです。

その前後では、beforeafterフィルタが呼ばれます。それぞれrouter.befiorerouter.afterイベンドが発火されます。


	public function dispatch(Request $request)
	{
		$this->currentRequest = $request;

		$response = $this->callFilter('before', $request);

		if (is_null($response))
		{
			$response = $this->dispatchToRoute($request);
		}

		$response = $this->prepareResponse($request, $response);

		$this->callFilter('after', $request, $response);

		return $response;
	}

3-7. Illuminate\Routing\Router#dispatchRoute()

Illuminate\Routing\Router#dispatch()から呼び出されます。

リクエスト内容から実行するRouteクラスを取得して、run()メソッドを実行します。これにより、app/routes.phpで定義したルーティングの内容が実行され、アプリケーションの処理が行われます。

run()メソッドの戻り値は、最終的にクライアントへのレスポンスとなります。

callRouteBefore()callRouteAfter()では、Routeクラスで定義したbeforeafterフィルタが実行されます。(ex. Route::get(‘/’, [‘before’ => ‘auth’,… ))

callRouterBefore()メソッドの戻り値がnull以外なら、run()メソッドは実行されないので、authフィルタなど、アプリケーション実行前にチェックをかける場合はこれを利用することになります。

	public function dispatchToRoute(Request $request)
	{
		$route = $this->findRoute($request);

        $this->events->fire('router.matched', array($route, $request));

		$response = $this->callRouteBefore($route, $request);

		if (is_null($response))
		{
			$response = $route->run($request);
		}

		$response = $this->prepareResponse($request, $response);

		$this->callRouteAfter($route, $request, $response);

		return $response;
	}

さいごに

Laravel フレームワークの起動から終了までの全体の流れを見てみました。

流れとしてはそれほど複雑ではないのですが、Stack/Builder によるミドルウェアやイベント、フィルタなど、多くの箇所でアプリケーション固有の処理を差し込めるような仕組みになっています。

拡張できるポイントを分かっておくと、いつどこでどのように処理を追加すれば良いかが判断しやすくなります。

こうしたフレームワークのコードを読む勉強会なんかもやってみたいですね。

  • コメント (Close): 0
  • トラックバック (Close): 0

Home > アーカイブ > 2014-03

検索
フィード
メタ情報

Return to page top