Symfony's magic: dependency injection

spO0q 🐒🎃 - May 17 '23 - - Dev Community

Injecting dependencies is a fundamental principle that is recommended by various approaches, including SOLID.

More pragmatically, instead of doing this:

namespace App\Services;

class MyService {
    private $myDependency;

    public function __construct()
    {
        $this->myDependency = new MyDependency();
    }
}
Enter fullscreen mode Exit fullscreen mode

You can do that (PHP 8):

namespace App\Services;

class MyService {

    public function __construct(private MyDependency $myDependency)
{}
}
Enter fullscreen mode Exit fullscreen mode

However, you may wonder why it's better and how it works behind the scene.

Injections and autowiring

In a standalone PHP project, you would need to create some class that will have a single but quite essential responsibility: read configs and instantiate objects.

Thanks to this class, you would be able to use the shortcut we saw earlier.

In Symfony, it's usually achieved with built-in interfaces and containers (e.g., ContainerBuilder, Dependency Injection component), which saves significant time and efforts.

It's not uncommon to read the term "service container." Symfony looks into the config folder to build its containers. That's why you have to write in specific config files.

Symfony does lots of magic behind the scene, providing features likes Autowiring.

By enabling Autowiring, you are telling Symfony to inject dependencies in services automatically:

# config/services.yaml
services:
    _defaults:
        autowire: true

    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}'
Enter fullscreen mode Exit fullscreen mode

Source: Symfony doc - Autowiring

It removes the hassle of manual declarations, and classes are "automatically registered as services and configured to be autowired."

This ways, you can load your dependencies automagically and use them in your controllers.

What's the impact on performances?

Behind the scene, Symfony makes several optimizations we won't see here, including caching and enhanced compilation.

That's why "there is no performance penalty for using autowiring," except on dev environments where the kernel will likely rebuild the container several times.

Ready-to-use containers

Apps powered by Symfony do not have to bother. The containers contains pretty much all services required.

They only have to type-hint them as arguments to start using them.

For example, if you need logs:

namespace App\Service;

use Psr\Log\LoggerInterface;

class MyClass
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }
}
Enter fullscreen mode Exit fullscreen mode

Source: Symfony docs - service config into a service

You can even now specify whether a service should be registered according to the environment thanks to PHP 8 attributes:

#[When(env: 'dev')]
class MyClass
{
}
Enter fullscreen mode Exit fullscreen mode

Wrap up

There's no ultimate approach, but dependency injection is far better for maintenance than manual coupling.

Symfony simplifies the process and runs major optimizations behind the scene, so you practically get this feature for free (in terms of performances) while respecting standards.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .