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!

Advertisements

5 Responses to “Using annotations with Symfony Dependency Injection Component”


  1. […] 22, 2010 Now we can use annotations to load our services definitions into the Symfony Dependency Injection […]


  2. […] Beispielsweise bei Doctrine Common ist eine dabei. Beispielsweise beschrieben hier und hier __________________ Alle Angaben unter zwei Kaffee sind ohne […]


  3. 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!


    • 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!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: