0

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.

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Wow, thanks for the effort. This is completely new approach to me. I have to read about CompilerPass. But my question here is: is this approach provides me a separate container with my tagged services? If yes, how can I access it?
@pawel.kalisz: why you want a separete container??? I suppose that isn't possibile. Read your question I see that "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." and this will avoid "by-hand" writing of all services :)
0

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.

4 Comments

was it what you where looking for?
Partially yes, I can achieve what I want, but not in the way I want ;). I mean thanks to your solution I can create a factory with plenty of method like 'getServiceOne', 'getServiceTwo', or even emulate container behavior with get('service.one'), and so on. What I can't do is use of container to create services in factory. I mean I have to provide all dependencies by hand in factory method, which is ok, this solution is one step before the aim of my question. All things consider, you solution is satisfiable and I'll use it but still I would use also a second (limited) container instance.THX!
But wouldn't you have to manually register your services with your second container aswell? :)
Hmm, that's an interesting question. I think the best solution would be to create a separate services.yml with services that I am interested in and tell dependency injection mechanism to put load this separate file into another container. This is what I am looking for and I don't know if it is possible. But I'm afraid it is not, because this approach requires from symfony to provide multiple containers at once, which is not possible as far as I know, is it?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.