Symfony2のControllerResolverを読む
"Create your own framework... on top of the Symfony2 Components"を読んで、Symfony2とSilexに興味がわいてきたので色々実験してみました。とりあえず"Create your own framework..."でやったCalendarをSilexでどう実装するかを考えてみます。
Silexといえば
$app->get('/hello/{name}', function ($name) use ($app) { return 'Hello '.$app->escape($name); }); $app->run();
こんな感じのコードというイメージがありますが、中身としてはpart6で出てきたSymfony\Component\HttpKernel\Controller\ControllerResolverの派生クラスであるSilex\ControllerResolverを使っていて、$app->get()の第二引数には無名関数以外のものも渡すことができます。
ではどのようなものを渡すことができるのか、ControllerResolver#getController()を見ると以下のようになっているようです。
1. コールバック配列。つまり array($class_name, $method_name) 又は array($obj, $method_name)
2. __invokeを実装するオブジェクト
3. __invokeを実装するクラスのクラス名
4. 関数名
5. クラス名::メソッド名
無名関数の場合は2.になります。"Create your own framework..."では
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( 'year' => null, '_controller' => 'LeapYearController::indexAction', )));
のようにルートを定義していたので、同様に、Silexでも
$app->get('/hello/{name}', 'LeapYearController::indexAction');
と書くこともできます。しかしこれはあまり嬉しくありません。LeapYearController::indexAction()に$nameを渡すことはできますが、無名関数を使った時とは異なり、サービスロケーターである$appを参照することはできないからです。
(2014/04/01追記:実は簡単にできました Symfony2/SilexのControllerResolverを読む2 - iakioの日記)
ではSymfony2の場合はどうなっているのでしょうか。Symfony2のコントローラーはController#getContainer()でDIコンテナを参照することができます。これは、Symfony2のControllerResolverはSymfony\Bundle\FrameworkBundle\Controller\ControllerResolverであり、そこでControllerResolver#createController()をオーバーライドして、コントローラーをnewした後にController#setContainer()を呼び出しているためです。
Silexでも同様のControllerResolverを作ることも可能です。非常に雑に書くとこんな感じ。
<?php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Silex\Application; use Silex\ControllerResolver; class MyControllerResolver extends ControllerResolver { function __construct($app) { $this->app = $app; } protected function createController($controller) { list ($obj, $method) = parent::createController($controller); $obj->app = $this->app; return array($obj, $method); } } class LeapYearController { function indexAction($year) { //... } } $app = new Application(); $app['resolver'] = $app->share(function () use ($app) { return new MyControllerResolver($app); }); $app->get('/is_leap_year/{year}', 'LeapYearController::indexAction'); return $app;
と、$app['resolver']に独自のControllerResolverのインスタンスを返す無名関数をセットしてやれば良いわけです。
とまあ長々と書きましたがこれはあくまで実験で、実際のところこういうやり方はSilexっぽくは無いと思いますw。ただ、これだけでもSilexやSymfony2がとても柔軟にできていることは実感できました。
続く予定。