bcastellano
5/1/2016 - 4:53 PM

Inject Doctrine repositories to services with compiler pass in Symfony 2

Inject Doctrine repositories to services with compiler pass in Symfony 2

parameters:
    # mandatory parameter for doctrine entity manager
    default_entity_manager_service: doctrine.orm.default_entity_manager
    # optional parameter to create repositories as shared services
    default_repository_class: Doctrine\ORM\EntityRepository

services:
    # example 1
    app_bundle.user.manager:
        class: AppBundle\Model\Manager\UserManager
        arguments: ["AppBundle:User"]
        tags:
            - { name: doctrine.repository, argument: 0 }
    
    # example 2
    app_bundle.post.manager:
        class: AppBundle\Model\Manager\PostManager
        arguments: ["AppBundle:Post"]
        tags:
            - { name: doctrine.repository, argument: 0, entity_manager: doctrine.orm.default_entity_manager, repository: app_bundle.post.repository }
<?php

namespace AppBundle\DependencyInjection\Compiler;

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

class InjectRepositoriesCompilerPass implements CompilerPassInterface
{
    /**
     * {@inheritDoc}
     */
    public function process(ContainerBuilder $container)
    {
        // find tagged services
        $services = $container->findTaggedServiceIds('doctrine.repository');

        // for each service tagged inject repositories
        foreach ($services as $key => $tags) {

            // get tagged service
            $definition = $container->getDefinition($key);

            foreach ($tags as $tag) {
                // replace each argument specified by tag
                $this->replaceArgument($container, $definition, $tag);
            }
        }
    }

    /**
     * Replaces arguments by entity repositories
     *
     * @param ContainerBuilder $container
     * @param Definition $definition
     * @param array $tag
     *  - argument <int> Argument to replace position. Mandatory
     *  - entity_manager <string> Service name for entity manager to use. Optional
     *  - repository <string> Name for repository service. Optional
     */
    protected function replaceArgument(ContainerBuilder $container, Definition $definition, array $tag)
    {
        // get object class as service argument
        $entityClass = $definition->getArgument($tag['argument']);

        // get entity manager service from tag or parameters
        $emServiceName = (isset($tag['entity_manager']) ? $tag['entity_manager'] : $container->getParameter('default_entity_manager_service'));

        if (isset($tag['repository'])) {
            // if repository is setted, then create repository as service
            $repositoryClass = $container->getParameter('default_repository_class');

            // get repository to replace
            $dmDefinition = (new Definition($repositoryClass, [$entityClass]))
                ->setFactory([
                    new Reference($emServiceName),
                    'getRepository'
                ]);

            // create repository as service
            $container->setDefinition($tag['repository'], $dmDefinition);

            // get repository service reference
            $replacedArgument = new Reference($tag['repository']);
        } else {
            // get repository to replace
            $replacedArgument = (new Definition(null, [$entityClass]))
                ->setFactory([
                    new Reference($emServiceName),
                    'getRepository'
                ]);
        }

        $definition->replaceArgument($tag['argument'], $replacedArgument);
    }
}
<?php

namespace AppBundle;

use AppBundle\DependencyInjection\Compiler\InjectRepositoriesCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AppBundle extends Bundle
{
    /**
     * @param ContainerBuilder $container
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        // add compiler pass
        $container->addCompilerPass(new InjectRepositoriesCompilerPass());
    }
}