Do you want to know why you shouldn’t inject the Symfony container in your services and learn a bit more about dependency injection? Don’t miss this piece.

My mama said never to inject Symfony containers…

— But mom, all Symfony tutorials around say I should use containers…

— And also that if you are a good kid, you will get a new PS4 from Santa Claus. Do not believe everything they say!

It’s true that most tutorials and Stack Overflow (please, do not try to convince me that you do not use Stack Overflow) answers use containers to get services. However, that doesn’t mean using it is the right way to have access to services.

— So why do they all say I should use containers?

Maybe they all say you should use services, and the simplest way to show how to use a service in a tutorial is with $container->get(‘your_service’). But most tutorials are about how to use a service and they assume you already know what dependency injection is.

You do know what dependency injection is, right?

Don’t worry, most people don’t know as well. Dependency injection is like love. Most people know exactly what love is until somebody actually asks them what it is.

Wikipedia says that

“Dependency injection is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time.”

And you say

— WHAT!?!?!?

Yeah… Ok… Dependency Injection is a design pattern in which you use constructors and setters to inject parameters (often called services) that they depend on. The container is the glue that allows the wiring of the service dependencies.

— Errrr… I…

Ok, using a PHP, the right way example. Normally you would have:

<?php

namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

 

But using dependency injection, you have:

<?php

namespace Database;

class Database
{
    protected $adapter;

    public function __construct(MySqlAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
}

class MysqlAdapter {}

 

And the autoloader — what almost every framework has — will use the container to wire the MySqlAdapter service to the Database class.

— Ah, now I got it. But why can’t I just inject the container as the tutorials explain?

<?php

class Database
{
    protected $adapter;

    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->adapter = $this->container->get('MysqlAdapter'):
    }
}

 

Ok, let me answer that. You shouldn’t pass the container because:

Container aware is not dependency injection

This may seem elementary, but the design pattern is dependency injection, not container injection.

Container aware makes the code hard to maintain

Let’s say you have 10 classes dependent on the MegaService and you just find out about the NewMegaService, which does the same thing as MegaService but much better.

If you use dependency injection, you can easily change your services config file to change the definition to inject MegaService instead NewMegaService. If you use container, you will have to change the hardcoded service in every class.

Your IDE likes it

The type hinting of the injected services will allow your IDE to know the type of code its dealing with, and help you with the programming boring tasks like finding out about constants and methods the service has.

For more information, it’s totally worth reading Dan Blow’s answer to In Symfony2, why is it a bad idea to inject the service container, rather than individual services?.

Your class should use only what it needs

It’s a good design practice to make your class use only what it needs. It doesn’t need a container, it just needs a service from the container so, give it what it needs.

Just as a notice, it’s not a bad practice to use setters to inject services instead of constructors. I’m saying this because I heard somebody saying that setters were just a workaround. They’re not!

Especially when you need a lot of services, it may make it easier to read the code to have getters and setters instead of having a constructor with lots of parameters. As everything in life, sometimes common sense is the best design pattern.

 

Written by Eduardo Fernandes | Developer at Cleverti