问题
Edit
after digging into the symfony code, particularly the ControllerResolver, it seems what im trying to do actually isnt possible unless i subclass/implement ControllerResolverInterface myself.
this is the following code which instantiates the controller passed from the route:
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
}
list($class, $method) = explode('::', $controller, 2);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
return array(new $class(), $method);
}
as you can see on the last line, this is always instantiated with no arguments passed, so i will have to override this method to inject something in that way. feels very hacky.
Original Question
I'm trying to figure out how I can inject services into a custom controller defined in dynamic routes using Symfony components (e.g. not the full stack framework).
Please note, I am not using the full stack framework and am not using their DemoBundle src code. I have a composer.json
file that requires components, so I have a custom index.php
file which is more or less the same as that detailed here:
http://fabien.potencier.org/article/55/create-your-own-framework-on-top-of-the-symfony2-components-part-12
I have the following:
$routes = new RouteCollection();
$routes->add(
'some route name',
new Route(
'a route path',
array(
'_controller' => 'App\MyBundle\Controller\MyController::handle'
)
)
);
Then I have the following within App/MyBundle/DependencyInjection/MyExtension.php
:
public function load(array $configs, ContainerBuilder $container) {
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resource/config')
);
$loader->load('services.xml');
}
App/MyBundle/Resources/config/services.xml
:
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="templating" class="Symfony\Component\Templating\EngineInterface" />
<service id="navigation" class="App\MyBundle\Controller\MyController">
<argument type="service" id="templating" />
</service>
</services>
</container>
I'm basically trying to get the templating service injected into the MyController
constructor, and my understanding is the MyExtension
file should be loaded automatically. I assume as I'm not using the full stack framework, this is the reason why, but how can I get this working?
回答1:
Nothing wrong with overriding ControllerResolver. The full stack framework does that too. Otherwise the Controllers couldn't be ContainerAware.
I also use Symfony Components without the full stack framework and, partly copying the full stack framework, I ended up with this in order to inject the container in my controllers
class ControllerResolver extends SymfonyControllerResolver
{
protected $container;
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
{
$this->container = $container;
parent::__construct($logger);
}
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller));
}
list($class, $method) = explode('::', $controller, 2);
$class = "Namespace\\Controllers\\" . $class;
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
return array($controller, $method);
}
}
If you wanted to add possibility to define controllers as services you can replace
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
With something like
if (!class_exists($class)) {
if (!$this->container->has($class)) {
throw new \Exception( ... );
}
$controller = $this->container->get($class);
return array($controller, $method);
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
return array($controller, $method);
回答2:
Well, at first. You don't have to inject services into your controller. A normal controller will extend Symfony\Bundle\FrameworkBundle\Controller\Controller
which gets the hole container injected. This means you can access the templating
service like this:
public function myAction()
{
$templating = $this->get('templating');
}
But Symfony2 gives you also the possibility of creating controller as services. That means you remove the extend from the default Controller
and instead of that only inject the services you need (usually request
and response
). More information can be found in this great post by Richard Miller.
You can also read this post by Lukas Kahwe Smith, in which he talks about why he thinks services are a 'best practise' (please note that Fabien, former of the Symfony project, disagrees with this).
来源:https://stackoverflow.com/questions/15658830/symfony-2-controller-dependencies-extending-containeraware