Home > アーカイブ > 2014-03
2014-03
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
について、オートロードによる読み込みが行われるので、Route
はIlluminate\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. インスタンスメソッドの実行
- で取得したインスタンスについて、指定されたインスタンスメソッド(この場合
get()
)を実行します。
さいごに
フレームワークのソースから Laravel のファサードクラスの仕組みを見てきました。
ファサードクラスの作り方として、ServiceProvider の構築が良く手順に含まれていますが、実はファサードクラスを作る上ではこれは必須ではありません。ファサードクラスが IoC コンテナからインスタンスを取得するため、そのインスタンスを事前にコンテナに設定するために ServiceProvider を利用することが多いだけです。
ファサードクラスを自作する際もこうした動きを知っていると作りやすいですね。
なお、Laravel のファサードクラスは、GoF のファサードパターンとは異なり、IoCコンテナにあるインスタンスを透過的に呼び出す仕組みとなっており、便利なサービスロケータと言った方がイメージしやすいかもしれません。( ちなみに、Laravel のファサードと GoF のファサードパターンは違うものだそうです。)
- コメント (Close): 0
- トラックバック (Close): 0
Vagrant 1.5 で追加された Rsync Synced Folder が良い
- 2014-03-13 (木)
- Vagrant
Vagrant 1.5 がリリースされました。1.1 以来の big change ということで、目新しい機能が追加されています。
目玉はやはり 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 up
、vagrant reload
、vagrant provision
、vagrant 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
最近の PHPer が集まれば、一度は話題に上がるのが、この composer install をどこで実行するのか問題。
これまで聞いた話をまとめると、大きく分けて、以下の2パターンになります。どちらの方法を取っているか教えて下さい 😀
0. 前提
前提ですが、以下のような方法で、Composer 関連のファイルは管理しているとします。おそらく多くはこのような形になっていると思います。
- PHP コードは、Git などの VCS で管理する。
- composer.json, composer.lock は、VCS で管理する。
- composer.phar, vendor/ は、VCS で管理しない。
また、今回対象としているのはアプリケーションで、Packagist に登録して、配布するようなフレームワークやライブラリは対象外です。
1. 本番サーバで実行
本番PHPサーバ上で composer install を実行するパターンです。
本番PHPサーバに何かしらの方法で、PHP コードと共にcomposer.json
、composer.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 コードからフレームワークの起動から終了までの流れを追う
Laravel フレームワークが起動してから終了するまでの流れについて、コードを読んでみました。
今回読んだフレームワークのバージョンは、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()
メソッドがアプリケーション実行のメインです。
その前後では、before
、after
フィルタが呼ばれます。それぞれrouter.befiore
、router.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
クラスで定義したbefore
、after
フィルタが実行されます。(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
- 検索
- フィード
- メタ情報