Completed
Push — master ( 5397e2...75d13e )
by Ben
04:56
created

RepositoryServicesCompilerPass::createArgument()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Lendable\DoctrineExtensionsBundle\DependencyInjection\CompilerPass;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Doctrine\ORM\Mapping\ClassMetadata;
9
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
10
use Lendable\DoctrineExtensionsBundle\Doctrine\Repository\RepositoryFactory;
11
use Lendable\DoctrineExtensionsBundle\Util\ConfigUtil;
12
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
13
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
14
use Symfony\Component\DependencyInjection\ContainerBuilder;
15
use Symfony\Component\DependencyInjection\Definition;
16
use Symfony\Component\DependencyInjection\Reference;
17
18
class RepositoryServicesCompilerPass implements CompilerPassInterface
19
{
20
    public function process(ContainerBuilder $container)
21
    {
22
        $config = ConfigUtil::fetch($container)['repositories'];
23
        assert(is_array($config));
24
25
        /** Map of name => definition of entity managers that have already been processed. */
26
        $configuredManagers = [];
27
28
        /** Map of manager name => definition of repository factories that have been processed. */
29
        $repositoryFactories = [];
30
31
        /** Ensures services are not tagged with the same manager & entity combination. */
32
        $managerEntityMap = [];
33
34
        foreach ($config as $repositoryFqcn => $repoConfig) {
35
            /** @var string[] $managerNames */
36
            $managerNames = $repoConfig['managers'];
37
            $entityFqcn = $repoConfig['entity'];
38
39
            foreach ($managerNames as $managerName) {
40
                if (isset($managerEntityMap[$managerName][$entityFqcn])) {
41
                    throw new InvalidConfigurationException(sprintf('The entity %s has been defined on multiple repository services for the same entity manager', $entityFqcn));
42
                }
43
44
                $managerEntityMap[$managerName][$entityFqcn] = true;
45
46
                if (!isset($configuredManagers[$managerName])) {
47
                    $repositoryFactories[$managerName] = $this->createAndRegisterRepositoryFactory($container, $managerName);
48
                    $configuredManagers[$managerName] = true;
49
                }
50
51
                $this->createRepositoryService($container, $managerName, $repositoryFqcn, $entityFqcn, $repositoryFactories[$managerName], $repoConfig);
52
            }
53
        }
54
    }
55
56
    private function createManagerConfigurationId(string $managerName): string
57
    {
58
        return sprintf('doctrine.orm.%s_configuration', $managerName);
59
    }
60
61
    private function createRepositoryFactoryDefinition(): Definition
62
    {
63
        $definition = new Definition(RepositoryFactory::class);
64
        $definition->setArguments(
65
            [
66
                new Reference('service_container'),
67
                [], // IDs to be populated later.
68
                new Definition(DefaultRepositoryFactory::class),
69
            ]
70
        );
71
72
        return $definition;
73
    }
74
75
    private function createAndRegisterRepositoryFactory(ContainerBuilder $container, string $managerName): Definition
76
    {
77
        $configuration = $container->getDefinition($this->createManagerConfigurationId($managerName));
78
79
        $repositoryFactory = $this->createRepositoryFactoryDefinition();
80
81
        $configuration->removeMethodCall('setRepositoryFactory');
82
        $configuration->addMethodCall('setRepositoryFactory', [$repositoryFactory]);
83
84
        return $repositoryFactory;
85
    }
86
87
    private function createRepositoryService(ContainerBuilder $container, string $managerName, string $repositoryFqcn, string $entityFqcn, Definition $repositoryFactoryDefinition, array $config): string
88
    {
89
        $repositoryDefinition = new Definition($repositoryFqcn);
90
        $repositoryDefinition->setPublic(true);
91
92
        // Create inline factory definitions.
93
94
        $managerDefinition = new Definition(EntityManagerInterface::class);
95
        $managerDefinition->setFactory([new Reference('doctrine'), 'getManager']);
96
        $managerDefinition->setArguments([$managerName]);
97
98
        $metadataDefinition = new Definition(ClassMetadata::class);
99
        $metadataDefinition->setFactory([$managerDefinition, 'getClassMetadata']);
100
        $metadataDefinition->setArguments([$entityFqcn]);
101
102
        // First 2 args are always EM & meta.
103
104
        $repositoryDefinition->addArgument($managerDefinition);
105
        $repositoryDefinition->addArgument($metadataDefinition);
106
107
        assert(is_array($config['args']));
108
109
        // Add custom args.
110
111
        foreach ($config['args'] as $arg) {
112
            $repositoryDefinition->addArgument($this->createArgument($arg));
113
        }
114
115
        $hash = hash('sha256', $entityFqcn);
116
        $id = sprintf('lendable.doctrine_extensions.repositories.%s.%s', $managerName, $hash);
117
118
        $fqcnToServiceIdMap = $repositoryFactoryDefinition->getArgument(1);
119
        $fqcnToServiceIdMap[$entityFqcn] = $id;
120
        $repositoryFactoryDefinition->replaceArgument(1, $fqcnToServiceIdMap);
121
122
        $container->setDefinition($id, $repositoryDefinition);
123
124
        return $id;
125
    }
126
127
    private function createArgument($configValue)
128
    {
129
        if (is_string($configValue) && $configValue[0] === '@') {
130
            return new Reference(mb_substr($configValue, 1));
131
        } elseif (is_array($configValue)) {
132
            $value = [];
133
134
            foreach ($configValue as $key => $configElement) {
135
                $value[$key] = $this->createArgument($configElement);
136
            }
137
138
            return $value;
139
        } else {
140
            return $configValue;
141
        }
142
    }
143
}
144