senenhermida.docs
  • Design Patterns
  • SOLID
  • PHP History
    • Late static binding
  • PHP Questions
  • Symfony
    • Service container
    • Event dispatcher
    • Security
    • Validation
    • Serializer
    • Http Kernel Component
    • Cache component
    • Symfony history
  • Algorithms
  • Leetcode
    • Codility
  • My CV
Powered by GitBook
On this page
  • Service container
  • Service container
  1. Symfony

Service container

Service container

Service container

Tool for managing class dependencies and performing dependency injection.

Automatic Service Loading

Thanks to this configuration, you can automatically use any classes from the src/ directory as a service, without needing to manually configure it.

If you'd prefer to manually wire your service, that's totally possible

# config/services.yaml
services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      
        # Automatically injects dependencies in your services.
        autoconfigure: true 
        # Automatically registers your services as commands, event subscribers, etc.
        # When a service is autoconfigured, it means that Symfony 
        # will automatically tag it when possible.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # order is important in this file because service definitions
    # always *replace* previous ones; add your own service configuration below

    # ...

Injecting services/config into a service

The container will automatically know to pass the logger service when instantiating the MessageGenerator thanks toautowiring. autowire:true in services.yml

Configure arguments

class MessageGenerator
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }
}
# config/services.yaml
services:
    # ... same as before

    # explicitly configure the service
    App\Service\SiteUpdateManager:
        arguments:
            $adminEmail: 'manager@example.com'

Tags

Service tags are a way to tell Symfony or other third-party bundles that your service should be registered in some special way. Take the following example:

# config/services.yaml
services:
    App\Twig\AppExtension:
        tags: ['twig.extension']

Services tagged with the twig.extension tag are collected during the initialization of TwigBundle and added to Twig as extensions.

Service parameters

services:
    App\Service\SomeService:
        arguments:
            $emailSender: '%email_sender%'

#[Required] in setter

same as

services:
    App\Service\MessageGenerator:
        # ...
        calls:
            - setLogger: ['@logger']

Choose a specific service

We inject LoggerInterface but it has multiple implementations such as logger, monolog.logger.request, monolog.logger.php

In these situations, the container is usually configured to automatically choose one of the service

But, you can control this and pass in a different logger:

# config/services.yaml
services:
    # ... same code as before

    # explicitly configure the service
    App\Service\MessageGenerator:
        arguments:
            # the '@' symbol is important: that's what tells the container
            # you want to pass the *service* whose id is 'monolog.logger.request',
            # and not just the *string* 'monolog.logger.request'
            $logger: '@monolog.logger.request'

public vs private services

By default every service is private. You cannot access them using $container->get()

As a best practice, you should only create private services and you should fetch services using dependency injection instead of using $container->get().

bind arguments globally

services:
    _defaults:
        bind:
            $emailNotificationsEnabled: '%email_notifications_enabled%'
namespace App\Service;

class EmailSender
{
    private $mailer;
    private $emailNotificationsEnabled;

    public function __construct(\Swift_Mailer $mailer, bool $emailNotificationsEnabled)
    {
        $this->mailer = $mailer;
        $this->emailNotificationsEnabled = $emailNotificationsEnabled;
    }

}

Later

  • more

    service subscriber and locator

    decorator service

    factory service

    service closures

    Injecting a Closure as an Argument ??

    Container Building Workflow

    Binding Arguments by Name or Type ??

A Compiler Pass in Symfony is a way to manipulate or modify service definitions within the dependency injection container before it is fully compiled and used by the application.

What It's Useful For:

  • Customizing Service Definitions: You can add, remove, or alter services and their dependencies programmatically.

  • Tag Processing: It allows you to find all services tagged with a specific label and apply custom logic, such as registering them in a registry service.

  • Advanced Dependency Injection: You can dynamically modify service configurations that can't be predefined in static YAML or XML files.

Example Use Case:

Suppose you have multiple services tagged as app.custom_logger. You can use a compiler pass to collect these services and inject them into a logger manager.

How It Works:

  1. Create the Compiler Pass Class: Extend CompilerPassInterface and implement the process method.

  2. Modify Service Container: Use the ContainerBuilder object to access and modify service definitions.

  3. Register the Compiler Pass: Add it to the Kernel in the build method.

Code Example:

php
Copy code
namespace App\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class CustomLoggerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->has('app.logger_manager')) {
            return;
        }

        $definition = $container->findDefinition('app.logger_manager');
        $taggedServices = $container->findTaggedServiceIds('app.custom_logger');

        foreach ($taggedServices as $id => $tags) {
            $definition->addMethodCall('addLogger', [new Reference($id)]);
        }
    }
}

Register it in Kernel:

php
Copy code
use App\DependencyInjection\CustomLoggerPass;

class Kernel extends BaseKernel
{
    protected function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new CustomLoggerPass());
    }
}

Summary:

Compiler Passes are powerful for customizing and optimizing service definitions during the container compilation process, providing flexibility for complex dependency injection scenarios.

PreviousSymfonyNextEvent dispatcher