Symfony - Service Container



In any application, objects tend to increase as the application grows. As objects increase, the dependency between the objects also increases. Object dependency needs to be handled properly for a successful application.

As discussed in the Components chapter, Symfony provides an easy and efficient component, DependencyInjection to handle object dependency. A service container is a container of objects with properly resolved dependency between them. Let us learn how to use DependencyInjection component in this chapter.

Let us create a Greeter class. The purpose of the Greeter class is to greet the user as shown in the following example.

$greeter = new Greeter('Hi'); 
$greeter->greet('Jon'); // print "Hi, Jon" 

The complete code of the Greeter class is as follows.

class Greeter { 
   private $greetingText; 
   
   public function __construct($greetingText) { 
      $this->greetingText = $greetingText; 
   }  
   public function greet($name) { 
      echo $this->greetingText . ", " . $name . "\r\n"; 
   } 
}

Now, let us add Greeter class to the service container. Symfony provides ContainerBuilder to create a new container. Once the container is created, Greeter class can be registered into it using the container's register method.

use Symfony\Component\DependencyInjection\ContainerBuilder; 
$container = new ContainerBuilder(); 
$container 
   ->register('greeter', 'Greeter') 
   ->addArgument('Hi');

Here, we have used static argument to specify the greeting text, Hi. Symfony provides a dynamic setting of parameter as well. To use a dynamic parameter, we need to choose a name and specify it between % and the parameter can be set using container's setParameter method.

$container = new ContainerBuilder(); 
$container 
   ->register('greeter', 'Greeter') 
   ->addArgument('%greeter.text%');  
$container->setParameter('greeter.text', 'Hi');

We have registered a Greeter class with proper setting. Now, we can ask the container to provide a properly configured Greeter object using the container get method.

$greeter = $container->get('greeter'); 
$greeter->greet('Jon'); // prints "Hi, Jon" 

We have successfully registered a class, Greeter into container, fetched it from the container and used it. Now, let us create another class User, which use Greeter class and see how to register it.

class User { 
   private $greeter;  
   public $name; 
   public $age;  
   
   public function setGreeter(\Greeter $greeter) { 
      $this->greeter = $greeter; 
   }  
   public function greet() { 
      $this->greeter->greet($this->name); 
   } 
}

The User class gets the Greeter class using one of its setter method, setGreeter. For this scenario, Symfony provides a method, addMethodCall and a class, Reference to refer another class as shown in the following code.

use Symfony\Component\DependencyInjection\Reference;  
$container 
   ->register('user', 'User') 
   ->addMethodCall('setGreeter', array(new Reference('greeter'))); 

Finally, we have registered two classes, Greeter and User having a strong relation between them. Now, we can safely fetch the User object with properly configured Greeter class from the container as shown in the following code.

$container->setParameter('greeter.text', 'Hi'); 
$user = $container->get('user'); 
$user->name = "Jon"; 
$user->age = 20; 
$user->greet(); // Prints "Hi, Jon"

We have seen how to configure an object in a container using PHP itself. Symfony provides other mechanisms as well. They are XML and YAML configuration files. Let us see how to configure a container using YAML. For this, install symfony/config and symfony/yaml components along with symfony/dependency-injection components.

cd /path/to/dir 
mkdir dependency-injection-example 
cd dependency-injection-example 
composer require symfony/dependency-injection 
composer require symfony/config 
composer require symfony/yaml

YAML configuration will be written in a separate file, services.yml. YAML configuration consists of two sections, parameters and services. Parameters section defines all required parameters. Services section defines all objects. Services section is further divided into multiple sections namely, class, arguments, and calls. Class specifies the actual class. Arguments specifies the constructor's arguments. Finally, calls specify the setter methods. Another class can be referred using @ symbol, @greeter.

parameters: 
   greeter.text: 'Hello' 
services: 
   greeter: 
      class: Greeter
      arguments: ['%greeter.text%'] 
   user: 
      class: User 
      calls: 
         - [setGreeter, ['@greeter']] 

Now, services.yml can be loaded and configured using FileLoader and YamlFileLoader as shown in the following code.

use Symfony\Component\Config\FileLocator; 
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;  

$yamlContainer = new ContainerBuilder(); 
$loader = new YamlFileLoader($yamlContainer, new FileLocator(__DIR__)); 
$loader->load('services.yml');  

$yamlUser = $yamlContainer->get('user'); 
$yamlUser->name = "Jon"; 
$yamlUser->age = 25; 
$yamlUser->greet(); 

The complete code listing is as follows.

main.php

<?php  
   require __DIR__ . '/vendor/autoload.php';  
   use Symfony\Component\DependencyInjection\ContainerBuilder; 
   use Symfony\Component\Config\FileLocator; 
   use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 
   use Symfony\Component\DependencyInjection\Reference;  
   
   class Greeter { 
      private $greetingText; 
      
      public function __construct($greetingText) {
         $this->greetingText = $greetingText; 
      }  
      public function greet($name) { 
         echo $this->greetingText . ", " . $name . "\r\n"; 
      } 
   }  
   class User { 
      private $greeter;  
      public $name; 
      public $age;  
      
      public function setGreeter(\Greeter $greeter) { 
         $this->greeter = $greeter; 
      }  
      public function greet() { 
         $this->greeter->greet($this->name); 
      } 
   }  
   $container = new ContainerBuilder(); 
   $container 
      ->register('greeter', 'Greeter') 
      ->addArgument('%greeter.text%');  
   $container 
      ->register('user', 'User') 
      ->addMethodCall('setGreeter', array(new Reference('greeter')));
   
   $container->setParameter('greeter.text', 'Hi'); 
   $greeter = $container->get('greeter'); 
   $greeter->greet('Jon'); 
   
   $user = $container->get('user'); 
   $user->name = "Jon"; 
   $user->age = 20; 
   $user->greet();  
   
   $yamlContainer = new ContainerBuilder(); 
   $loader = new YamlFileLoader($yamlContainer, new FileLocator(__DIR__)); 
   $loader->load('services.yml');  

   $yamlHello = $yamlContainer->get('greeter'); 
   $yamlHello->greet('Jon'); 
   
   $yamlUser = $yamlContainer->get('user'); 
   $yamlUser->name = "Jon"; 
   $yamlUser->age = 25; 
   $yamlUser->greet();  
?>

services.yml

parameters: 
   greeter.text: 'Hello' 
services: 
   greeter: 
      class: Greeter 
      arguments: ['%greeter.text%'] 
   user: 
      class: User 
      calls: 
         - [setGreeter, ['@greeter']] 

Symfony web framework uses the dependency injection component extensively. All the components are bound by the centralized service container. Symfony web framework exposes the container in all its Controller through container property. We can get all object registered in it, say logger, mailer, etc., through it.

$logger = $this->container->get('logger'); 
$logger->info('Hi'); 

To find the object registered in the container, use the following command.

cd /path/to/app 
php bin/console debug:container

There are around 200+ objects in the hello web app created in the installation chapter.

Advertisements