Service Container and Dependency Injection in Symfony framework

Sometimes a single idea can change everything about programming. For me, one of those ideas was the concept of a service container. It's like the holy grail). Actually, the idea itself is not specific to Symfony framework, it's just one of the successful patterns of application design often used in Symfony.

In a nutshell, a service-oriented architecture allows you to register a class (service) that can be retrieved from anywhere in the application by knowing its identifier. So, we say: "Container, give us this particular service", and we get an object ready to use.

But, in my opinion, the genius is not so much in the idea of a service container itself, but in Dependency Injection.

Let's try to explain what it is.

Imagine a situation where you have a class whose object cannot work independently - it needs other objects (if you think such a situation is unlikely or unnecessary - read this note a little later =)).

Dependency Injection allows us to resolve these dependencies. In other words, we can not only say "Container, give us this particular service", but also "Container, give us this particular service with all the necessary dependencies". The service will depend on other services, but externally we won't even notice it.

Let's move on to practice.

To get a service from a controller, it's enough to call:

class HelloController extends Controller
{
    // ...
    public function sendEmailAction()
    {
        // ...
        $mailer = $this->get('my_mailer');
        $mailer->send('ryan@foobar.net', ...);
    }
}
In Symfony framework, the service configuration can be done in different formats (yml, xml, php), in our examples we'll use yml:
# app/config/config.yml
services:
    my_mailer:
        class:        AcmeHelloBundleMailer
        arguments:    [sendmail]
Naturally, services can be used not only in controllers. It's good practice not to inject the entire container into a service (we're talking about dependencies), but only to inject the necessary service.

For example, if there are two services, one can be injected into another:

# app/config/config.yml
services:
    service_foo:
        class:        AcmeHelloBundleSomeFeatureFoo
        arguments:    [@service_goo]
    service_goo:
        class:        AcmeHelloBundleSomeFeatureGoo
If we now call in a controller
$mailer = $this->get('service_foo');
, we will get an instance of Foo with Goo already injected. arguments - what we pass to the constructor. Example classes:
<?php

namespace AcmeHelloBundleSomeFeature

class Foo { private $goo;

public function __construct(Goo $goo) { $this->goo = $goo } }

<?php

namespace AcmeHelloBundleSomeFeature

class Goo { }
Dependency Injection can be done not only through constructors, but also with the help of a setter method. Let's modify the previous example:
# app/config/config.yml
services:
    service_foo:
        class:        AcmeHelloBundleSomeFeatureFoo
        calls:    
            - [setGoo, ["@service_goo"]]
    service_goo:
        class:        AcmeHelloBundleSomeFeatureGoo
<?php

namespace AcmeHelloBundleSomeFeature

class Foo { private $goo;

public function setGoo(Goo $goo) { $this->goo = $goo } }

In a similar way, you can inject parameters, just use the "%" symbol on both sides instead of the "@" symbol:
# app/config/config.yml
parameters:
    my_mailer.class:      AcmeHelloBundleMailer
    my_mailer.transport:  sendmail

services: my_mailer: class: "%my_mailer.class%" arguments: ["%my_mailer.transport%"]

This is where the basic introduction ends =)