こんにちはエンジニアの @localdisk です。みなさんはLaravel 5.4 で追加された RealTime Facades をご存知でしょうか? あまり話題にはなっていないし、私自身も使ったことはないのですが、先日 Real Time Facades のことをふと思い出しついでにどのように動いているのか調べてみました
RealTime Facades?
RealTime Facades とは、Facade として作成されていないクラスを、あたかもFacadeの如く使用できる機能です。もちろん Illuminate\Support\Facades\Facade
クラスの継承を必要としません。例えば下記 Twitter クラスの名前空間の最初に Facades
を追加するだけで Twitter クラスは Facade として機能します。
<?php namespace App\Services; class Twitter { public function tweet($message) { var_dump($message) } }
<?php // 名前空間の最初に Facades をつけるだけ \Facades\App\Services\Twitter::tweet('hello');
このコードが何故動くのか? 少し気になったので調べてみました。結論としては黒魔術というよりは原始的な仕組みを利用した呪術に近いのではないか、と私は思っています。
spl_autoload_register
Composer 登場以前の私たちは spl_autoload_register
を利用して、積み重なる require
から逃れようと四苦八苦していました。一定年齢以上の PHPer 、あるいはレガシーシステムを運用している PHPer の皆様におかれましては鮮明に思い出せるのではないでしょうか。
Comoser がそれを隠蔽し秩序をもたらしたことで、それらの(忌まわしい)記憶は頭の隅に追いやられていたかもしれませんが、少し間だけ思い出してください。
spl_autoload_register
は未定義のクラスのロードを試みる __autoload
の実装を特定のメソッド、あるいは関数に実行させるよう登録することができます。
Illuminate\Foundation\AliasLoader
の prependToLoaderStack
メソッドで spl_autoload_register
が使われており AliasLoader
の load
メソッドが登録されています。このメソッドが RealTime Facades の肝です。
RealTime Facades の呪術
RealTime Facades を読み解くために Illuminate\Foundation\AliasLoader
クラスの関わりのある部分を抜粋しました。 load
メソッドの最初の if
に注目してください。 未定義のクラスが $facadeNamespace
で始まっている場合(そう Facades
で始まる場合です) loadFacade
メソッドをコールします。
そして loadFacade
メソッドは ensureFacadeExists
の結果を require しています。
ensureFacadeExists
は特定のパス・命名規約でクラスを生成しそのパスを返却しています。つまり Facade クラスを生成して require
してるだけなのです。読み解いていくとその仕組みは単純といえます。
<?php class AliasLoader { /** * The namespace for all real-time facades. * * @var string */ protected static $facadeNamespace = 'Facades\\'; /** * Load a class alias if it is registered. * * @param string $alias * @return bool|null */ public function load($alias) { if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) { $this->loadFacade($alias); return true; } if (isset($this->aliases[$alias])) { return class_alias($this->aliases[$alias], $alias); } } protected function loadFacade($alias) { require $this->ensureFacadeExists($alias); } protected function ensureFacadeExists($alias) { if (file_exists($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) { return $path; } file_put_contents($path, $this->formatFacadeStub( $alias, file_get_contents(__DIR__.'/stubs/facade.stub') )); return $path; } protected function formatFacadeStub($alias, $stub) { $replacements = [ str_replace('/', '\\', dirname(str_replace('\\', '/', $alias))), class_basename($alias), substr($alias, strlen(static::$facadeNamespace)), ]; return str_replace( ['DummyNamespace', 'DummyClass', 'DummyTarget'], $replacements, $stub ); } }
まとめ
RealTime Facades の仕組みを読み解いてみました。なかなか面白いでしょう? 私は読み解くまでこの機能がどのように実装されているか思いつきませんでした。このようにフレームワークやライブラリのコードを読み解くことはその理解を深め、自分になかった新しい考えを吸収するとてもよい方法だと思います。
みなさんもぜひ試してみてください。
私はもう少し RealTime Facades の使い所を考えてみたいと思います。もしかしたら見つからないかもしれませんが…。*1
*1:Mockが作りやすくなるという恩恵はあるが Facade にせず DI すればいいのでは…という気がしなくもない