前提
下記のようなコントローラクラス・ルーティングがあって、指定のルートが呼ばれた際(=コントローラクラスがインスタンス化されるとき)にコンストラクタ引数に入っているクラスを勝手にインスタンス化して入れてほしい。
<?php
namespace App\Api\Controller;
use Symfony\Component\HttpFoundation\Request;
use App\Api\Service\PingService;
// controller class
class PingController extends ControllerBase
{
private $pingService;
public function __construct(
PingService $pingService
) {
$this->pingService = $pingService;
}
public function get(Request $request)
{
$id = $request->get('id', 1);
return $this->returnJsonResponse([
'ping' => $this->pingService->getPing(),
'pong' => $this->pingService->getPong($id),
]);
}
}
// routing
$silexApplication->get('/api/ping', 'App\Api\Controller\PingController::get');
PHP-DI を使う
そこで、PHP-DI
という上記のようなことを手っ取り早く実現するためのライブラリがあるので composer require
で入れる。
composer require php-di/php-di
コンテナビルダー
下記のような ContainerBuilder
というシングルトンなクラスを作る。
パフォーマンスに影響があるようなので、本番向けにはキャッシュの設定などをしたほうが良さそう(公式は APC
を使うことを推奨している模様。なお、PHP5.5以降では APCu
ですね)。
<?php
namespace App;
use DI\Container;
use Doctrine\Common\Cache\ApcuCache;
class ContainerBuilder
{
public static function getInstance()
{
static $instance;
if (!($instance instanceof Container)) {
$builder = new \DI\ContainerBuilder();
$cache = new ApcuCache();
$builder->setDefinitionCache($cache);
$builder->writeProxiesToFile(false);
$instance = $builder->build();
}
return $instance;
}
}
コントローラリゾルバ
標準で使っているリゾルバ(Silex 2.x では Symfony\Component\HttpKernel\Controller\ControllerResolver
)を継承したクラスを作り、instantiateController()
をオーバーライドする。
デフォルトではただ単に new $class()
として、渡された名前のコントローラクラスをインスタンス化しているだけなので、PHP-DI の機能を使い解決させる。
<?php
namespace App;
class ControllerResolver extends \Symfony\Component\HttpKernel\Controller\ControllerResolver
{
protected function instantiateController($class)
{
$di = ContainerBuilder::getInstance();
return $di->make($class);
}
}
Silex\Application に上記のクラスを渡してやる
下記のようにして Application インスタンス(実態は pimple
によるDIコンテナ)に上記で作ったコントローラーリゾルバのインスタンスを入れる。
<?php
namespace App;
use Silex\Application;
$app = new Application();
$app['resolver'] = new ControllerResolver();