Skip to content
Advertisement

Symfony – How do I get services dynamically from the container?

For an API project I want to fail very early, before the controller is dispatched, if the input data is incorrect. I’ve managed to get the validation done by using the route defaults and an event subscriber subscribing to the KernelEvents::REQUEST event.

As you can see in the code, I’m trying to get the validator from the container. My assumption here is, that because auto wire is on, it should find them, but has() is never returning true.

So what exactly am I doing wrong? I’m trying to resolve the validator automatically, I would prefer to not have to declare it explicitly in the services.yml file.

Controller (excerpt):

    #[Route('/sign-up', name: 'sign_up', defaults: ['_validator' => SignUpValidator::class],methods: ['POST'])]
    public function register(int $page): Response
    {
        return new Response();
    }

Event Subscriber (excerpt):

    public function __construct(
        protected readonly Container $container
    ) {}
    
    protected function resolveValidator(Request $request): ?RequestValidatorInterface
    {
        $validatorClass = (string)$request->attributes->get('_validator');

        if ($this->container->has($validatorClass)) {
            return $this->container->get($validatorClass);
        }

        return null;
    }
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:

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.

    # 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/Entity/'
            - '../src/Kernel.php'

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
    PsrContainerContainerInterface: '@service_container'
    SymfonyComponentDependencyInjectionContainer: '@service_container'

Advertisement

Answer

While it is no longer possible to obtain a private service from the main services container, you have the option of making it public. That would require extra developer work of course, but this will prevent it from being automatically removed/inlined and will make it available directly in the services container.

By the way, autowiring only works if the service is defined as an argument of your controller/method. In your case, Symfony does not consider the default values for the Route annotation when determining which service to inject.

If you do not want to make these types of validator services public, a possible approach is to create a custom Service Locator with all possible validator services. You can create a service tag (e.g. app.request_validator) for these services and use a flagged interface (RequestValidatorInterface) to autoconfigure them. This will allow you to manage and access all validator services through a single, unified interface:

class SignUpValidator implements RequestValidatorInterface
{
    // ...
}

In any DI extension context:

$container->registerForAutoconfiguration(RequestValidatorInterface::class)
    ->addTag('app.request_validator');

Then, your Event Subscriber can be injected with this small locator instead:

AppEventSubscriberRequestValidatorSubscriber:
    arguments:
        - !tagged_locator 'app.request_validator'

The RequestValidatorSubscriber can use PsrContainerContainerInterface as type for this locator argument.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement