pawel.kalisz
pawel.kalisz

Reputation: 1245

Symfony2 dependency injection container sub-container

Is it possible to put some services in to another container which will be narrowed to a specific set o services? Or to separate some specific services to some kind of sub-container? I am asking this question because I need to inject a set of services into another service, and putting a whole container into service is , I belive, bad idea. Of course I can create a property in my service class for all services that I want to inject, but this is a solution that I am trying to avoid.

I will appreciate any help, and if you need more info about the problem just ask.

Upvotes: 0

Views: 956

Answers (2)

DonCallisto
DonCallisto

Reputation: 29912

Basically you can use the DIC by writing some services "tagged" with a special name.
For this you need to define your services into a file (following DIC specification) and tagging them in a particular way (Code will be took by Sonata Admin Bundle in this case, for explaination)

# MyBundle/Resources/config/admin.yml
services:
    sonata.admin.tag:
        class: YourNS\AdminBundle\Admin\BlogAdmin
        tags:
            - { name: sonata.admin, manager_type: orm, group: posts, label: "Blog" }
        arguments:
            - ~
            - YourNS\AdminBundle\Entity\Course
            - 'SonataAdminBundle:CRUD'
        calls:
            - [ setTranslationDomain, [YourNSAdminBundle]]

In this case i'm defining a service called sonata.admin.tag that is tagged with sonata.admin tag.
I can define dozen of those, all with sonata.admin.tag tag name.

Once I've done this, I have to create a "special" file (that I'll place into bundle's DependencyInjection folder [for convention]) that is a CompilerPass file.
What is a CompilerPass file?

Compiler passes give you an opportunity to manipulate other service definitions that have been registered with the service container.[...] One of the most common use-cases of compiler passes is to work with tagged services (read more about tags in the components section "Working with Tagged Services").

and this is exactly what you need!

Now you have to search (into this file) for services tagged with (in this specific example) sonata.admin

class AddDependencyCallsCompilerPass implements CompilerPassInterface
{
  /**
  * {@inheritDoc}
  */
    public function process(ContainerBuilder $container)
    {
        $groupDefaults = $admins = $classes = array();

        $pool = $container->getDefinition('sonata.admin.pool');

        foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $tags) {
            foreach ($tags as $attributes) {
                $definition = $container->getDefinition($id);

                $arguments = $definition->getArguments();

                if (strlen($arguments[0]) == 0) {
                    $definition->replaceArgument(0, $id);
                }

                if (strlen($arguments[2]) == 0) {
                    $definition->replaceArgument(2, 'SonataAdminBundle:CRUD');
                }

                $this->applyConfigurationFromAttribute($definition, $attributes);
                $this->applyDefaults($container, $id, $attributes);

                $arguments = $definition->getArguments();

                $admins[] = $id;
                //other logic here
                $pool->addMethodCall('setAdminClasses', array($classes));

As you can see here, we are searching for services tagged with sonata.admin ($container->findTaggedServiceIds('sonata.admin')) and we add those (in this case, that is specific for sonata admin bundle) to a $pool that is a ContainerBuilder

Now, we have to register a CompilerPass into our bundle file (the one you create before register bundle into application)

class SonataAdminBundle extends Bundle
{
    /**
    * {@inheritDoc}
    */
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new AddDependencyCallsCompilerPass());
    }
}

Now, you have registered some kind of services only for this bundle. A service factory is more oriented for

Symfony2's Service Container provides a powerful way of controlling the creation of objects, allowing you to specify arguments passed to the constructor as well as calling methods and setting parameters. Sometimes, however, this will not provide you with everything you need to construct your objects.

or view it more like to an "entry point" for services instantiation.

Upvotes: 1

Nicolai Fröhlich
Nicolai Fröhlich

Reputation: 52483

Injecting the whole container is indeed not recommeded for performance and testability reasons.

If constructor or getter/setter injection for all dependencies is not the way you want to go...

... a Service Factory is what you're looking for.

This way you can construct a service holding your other services and inject only this one.

That would be kind of the "subcontainer" you are talking about.

Upvotes: 0

Related Questions