Using annotations with Symfony Dependency Injection Component
January 20, 2010
Having worked a lot with Spring Framework for Java EE projects, I was looking for an Inversion of Control container for managing Dependency Injection in PHP in my Zend Framework applications instead of factories. I closely followed Fabien Potencier’s (Symfony’s project lead) series on Dependency Injection in spring (season) 2009 and I recently took the time to begin testing the Dependency Injection container from Symfony Components. Quite satisfied with it I decided to integrate it with Zend Framework and to add the possibility to use docblock annotations to describe services with a new loader for the component.
I will not cover here the benefits of using a Dependency Injection container in your applications, refer to all the resources you can find on the web. As well, I will not debate about the pertinence to use a DI container with dynamic languages such as PHP nor Ruby.
So let’s start with the annotation loader. I developed a class called LoSo_Symfony_Components_ServiceContainerLoaderAnnotations which extends sfServiceContainerLoader. I use Zend_Reflection for the introspection of docblocks and his ability to retrieve annotation tags. The class is available on my GitHub.
Let’s see an example of an annotation described service:
/**
* Description of Default_Service_TestService
*
* @author Loïc Frering <loic.frering@gmail.com>
* @Service
*/
class Default_Service_TestService
{
/**
* @var Default_Service_TestService2
*/
protected $_testService2;
/**
* @param Default_Service_TestService2 $testService2
* @return Default_Service_TestService
* @Inject
*/
public function setTestService2($testService2)
{
$this->_testService2 = $testService2;
return $this;
}
public function test()
{
return 'Test method from TestService';
}
public function test2()
{
return $this->_testService2->test2() . ' called from testService';
}
}
@Service annotation
The @Service annotation register the class as a service managed by the DI Container. If no string follows the @Service annotation, the class name will be used to determine the service identifier:
- Default_Service_TestService will be identified as testService
- MyService will be identified as myService
You can also explicitly specify the service identifier:
/** * @Service myService */
@Inject annotation
The @Inject annotation specify a dependence that need to be injected by the container. You can use the annotations in three different ways.
Constructor injection
@Inject annotated constructor arguments will be introspected and declared in the Service Container for injection. The argument name will be used to determine the service id to inject.
/**
* @Service
*/
class Default_Service_TestService
{
/**
* @var Default_Service_MyService
*/
protected $_myService;
/**
* @Inject
*/
public function __construct($myService)
{
$this->_myService = $myService;
}
// ....
}
Property injection
When the loader encounters a properties with the @Inject annotation, a method call will be declared in the service container to make the injection. A _testService or testService property will register a method call to setTestService with the testService as service identifier for injection.
/**
* @Service
*/
class Default_Service_TestService
{
/**
* @var Default_Service_MyService
* @Inject
*/
protected $_myService;
public function setMyService($myService)
{
$this->_myService = $myService;
}
// ....
}
You can also explicitly specify the service identifier:
/** * @Inject myService */
Setter injection
A setter method with @Inject annotation will also register a method call for a service injection. The service identifier to inject, if not explicitly defined with the annotation, will be determined with the method name : testService service will be injected in the setTestService method.
/**
* @Service
*/
class Default_Service_TestService
{
/**
* @var Default_Service_MyService
*/
protected $_myService;
/**
* @Inject
*/
public function setMyService($myService)
{
$this->_myService = $myService;
}
// ....
}
Using the loader
Using the loader is as simple as using XML or YAML loaders. Just pass to the loader constructor a path to a directory to be scanned by the loader:
$container = new sfServiceContainerBuilder(); $loader LoSo_Symfony_Components_ServiceContainerLoaderAnnotations($container); $loader->load($path);
The Need for Speed
As you might have wondered, using annotations introspection on each request is not very efficient. Not a problem: have a look at Symfony Dependency Injection component documentation and paticularly at the chapter named The Need for Speed.
Did you read it ? So you just have to use the PHP dumper to generate the plain optimized PHP code for your Service Container with the definition corresponding to what you have declared with your annotations.
$dumper = new sfServiceContainerDumperPhp($container);
file_put_contents($file, $dumper->dump(array('class' => 'MyContainer'));
Then stock the generated file when it does not exist on the first request and use the generated class on the following request.
Coming soon
Next post will be about elegant integration of Symfony Dependency Injection component with Zend Framework. You will be able to inject your services in your controllers with a simple @Inject annotation!
Like this:
Filed in Development
Tags: annotations, dependencyinjection, php, symfony, zendframework
January 22, 2010 at 11:35
[...] 22, 2010 Now we can use annotations to load our services definitions into the Symfony Dependency Injection [...]
January 10, 2011 at 11:35
[...] Beispielsweise bei Doctrine Common ist eine dabei. Beispielsweise beschrieben hier und hier __________________ Alle Angaben unter zwei Kaffee sind ohne [...]
February 4, 2011 at 21:12
You might also want to try Ding (http://marcelog.github.com/Ding), since it shares the same need (to have/use a spring-alike framework for php). Supports some JSR annotations and implements most of the spring’s container features (and some more).
In terms of performance, cache are used instead of php code, which results in faster execution as well.
I hope you enjoy it as much as I enjoyed writing it
Regards!
February 4, 2011 at 21:44
With Symfony2 DIC, Zend Framework, Doctrine2 and LoSoLib, I got everything I need for now in PHP’s way of doing things.
But Ding’s implementation seems quite interesting, thanks for the information!