Symfony2 avoiding container injection


Since I started using SensioLabs insight code analysis tool, I kept on getting a warning which advised against using Container Injection on my services. It was not the first time I heard this kind of message, Last year we submitted one of our projects to the SensioLabs team for evaluation, the client wanted to find out if we were following all the Symfony2 standards since he had enterprise ambitions for his project. Any ways the report was both good and not so great. We were doing okay with most of the stuff except for a couple of things:

  • Container Injection (Bad)
  • Fat controllers (Bad) – will cover this in more detail on another post
  • Unit testing (Not great) will cover this in more detail on another post

Besides those three things mentioned above, our team was not half bad – But it was an eye opener.

Being a victim of container injection is very easy, I did it because I only had to inject one service but still have access to all the services defined in the application – I mean really who would not want that, There are various reasons why you should not inject the container directly but the one I know of personally from experience has to do with Unit-testing your code. When want to start creating mock objects to test your service, it becomes a cumbersome job since your service only accepts 1 parameter and yet you are using more than 5 different services inside your one service – as the application grows so does the work of locating all these services being utilized so you can mock them. A lesson was learnt and a hard one indeed, But we never gave in – we just got better at it thanks to JMSDiExtraBundle.

What is JMSDiExtraBundle?

JMSDiExtraBundle adds more powerful dependency injection features to Symfony2:

  • configure dependency injection via annotations
  • convention-based dependency injection in controllers
  • aspect-oriented programming capabilities for controllers

NB: You can still inject individual services as arguments into your service using services.yml or services.xml

If you have not yet seen this bundle, I would recommend you head over to it’s website JMSDIExtraBundle, They have good documentation on both installation and usage – I will only cover the bit which shows how to inject services(DI) using the bundle as well as injecting config parameters into your service.

I have attached sample code to showcase how you can use JMSDIExtraBundle to define your service using annotations and inject relevant services. Please see blow.

Now lets explain what how the file is put together,  We first include the JMSDIExtraBundle into our class by using the following snippet:

use JMS\DiExtraBundle\Annotation as DI;

As part of your include you can be more specific if you just want to use “Inject” or “Tag”

use JMS\DiExtraBundle\Annotation\Tag;
use JMS\DiExtraBundle\Annotation\Inject;

Once the include is done, we can now give a name to our service – “This is what we would normal do via the services.yml or services.xml file”

* @DI\Service("my_project.send_account_created_email")
* @DI\Tag("kernel.event_subscriber")

Since our class is an Event Listener we need to also add a tag to let the service know how to plugin into the symfony kernel.

Lastly we inject our services into the service constructor, we only inject what we need  in the service and we can easily create mock objects to test the service constructor.

     * Class construct
     * @param Logger      $logger  
     * @param UserManager $userManager
     * @param Mailer      $mailer
     * @param Templating  $templating  
     * @DI\InjectParams({
     *     "logger"       = @DI\Inject("logger"),
     *     "userManager"  = @DI\Inject("my_project.user_manager"),
     *     "mailer"       = @DI\Inject("Mailer"),
     *     "templating"   = @DI\Inject("templating"),
     * })
     * @return void
    public function __construct(
            Logger $logger,
            UserManger $userManager,            
            \Swift_Mailer $mailer,
        $this->logger = $logger;
        $this->userManager = $userManager;
        $this->mailer = $mailer;
        $this->templating = $templating;

Once more thing I wanted to show is injecting configuration parameters to the service.

     * From email name
     * @var String
     * @@DI\Inject("%mailer_from_name%")

    public $fromName;  
     * From email address
     * @var String
     * @DI\Inject("%mailer_from_email%")

    public $fromEmailAddress;    
     * Root path
     * @var String
     * @Inject("%kernel.root_dir%")

    public $rootDir;

NB: You can also achieve all of this using services.xml or services.yml

I will not cover the Event Listener approach I used on this sample file on this post but be on the look out for a post dedicated to event listeners.

For more reading on Dependency Injection, I would recommend the symfony cookbook and this blog by Richard Miller.

Thanks for reading, Bye.

The following two tabs change content below.

Mfana Ronald Conco