‪TYPO3CMS  10.4
ServiceProviderCompilationPass.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21 use Symfony\Component\DependencyInjection\ContainerBuilder;
22 use Symfony\Component\DependencyInjection\Definition;
23 use Symfony\Component\DependencyInjection\Reference;
24 
28 class ‪ServiceProviderCompilationPass implements CompilerPassInterface
29 {
33  private ‪$registry;
34 
39 
44  public function ‪__construct(‪ServiceProviderRegistry ‪$registry, string ‪$registryServiceName = 'service_provider_registry')
45  {
46  $this->registry = ‪$registry;
47  $this->registryServiceName = ‪$registryServiceName;
48  }
49 
55  public function ‪process(ContainerBuilder $container): void
56  {
57  // Now, let's store the registry in the container (an empty version of it... it has to be added dynamically at runtime):
58  $this->‪registerRegistry($container);
59 
60  foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
61  $this->‪registerFactories($container, $serviceProviderKey);
62  }
63 
64  foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
65  $this->‪registerExtensions($container, $serviceProviderKey);
66  }
67  }
68 
72  private function ‪registerRegistry(ContainerBuilder $container): void
73  {
74  $definition = new Definition(ServiceProviderRegistry::class);
75  $definition->setSynthetic(true);
76  $definition->setPublic(true);
77 
78  $container->setDefinition($this->registryServiceName, $definition);
79  }
80 
85  private function ‪registerFactories(ContainerBuilder $container, string $serviceProviderKey): void
86  {
87  $serviceFactories = $this->registry->getFactories($serviceProviderKey);
88 
89  foreach ($serviceFactories as $serviceName => $callable) {
90  $this->‪registerService($container, $serviceName, $serviceProviderKey, $callable);
91  }
92  }
93 
98  private function ‪registerExtensions(ContainerBuilder $container, string $serviceProviderKey): void
99  {
100  $serviceExtensions = $this->registry->getExtensions($serviceProviderKey);
101 
102  foreach ($serviceExtensions as $serviceName => $callable) {
103  $this->‪extendService($container, $serviceName, $serviceProviderKey, $callable);
104  }
105  }
106 
113  private function ‪registerService(
114  ContainerBuilder $container,
115  string $serviceName,
116  string $serviceProviderKey,
117  callable $callable
118  ): void {
119  if (!$container->has($serviceName)) {
120  // Create a new definition
121  $factoryDefinition = new Definition();
122  $container->setDefinition($serviceName, $factoryDefinition);
123  } else {
124  // Merge into an existing definition to keep possible addMethodCall/properties configurations
125  // (which act like a service extension)
126  // Retrieve the existing factory and overwrite it.
127  $factoryDefinition = $container->findDefinition($serviceName);
128  if ($factoryDefinition->isAutowired()) {
129  $factoryDefinition->setAutowired(false);
130  }
131  }
132 
133  $className = $this->‪getReturnType($this->‪getReflection($callable), $serviceName) ?? 'object';
134  $factoryDefinition->setClass($className);
135  $factoryDefinition->setPublic(true);
136 
137  $staticallyCallable = $this->‪getStaticallyCallable($callable);
138  if ($staticallyCallable !== null) {
139  $factoryDefinition->setFactory($staticallyCallable);
140  $factoryDefinition->setArguments([
141  new Reference('service_container')
142  ]);
143  } else {
144  $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'createService' ]);
145  $factoryDefinition->setArguments([
146  $serviceProviderKey,
147  $serviceName,
148  new Reference('service_container')
149  ]);
150  }
151  }
152 
159  private function ‪extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable): void
160  {
161  $finalServiceName = $serviceName;
162  $innerName = null;
163 
164  $reflection = $this->‪getReflection($callable);
165  $previousClass = $container->has($serviceName) ? $container->findDefinition($serviceName)->getClass() : null;
166  $className = $this->‪getReturnType($reflection, $serviceName) ?? $previousClass ?? 'object';
167 
168  $factoryDefinition = new Definition($className);
169  $factoryDefinition->setClass($className);
170  $factoryDefinition->setPublic(true);
171 
172  if ($container->has($serviceName)) {
173  [$finalServiceName, $previousServiceName] = $this->‪getDecoratedServiceName($container, $serviceName);
174  $innerName = $finalServiceName . '.inner';
175 
176  $factoryDefinition->setDecoratedService($previousServiceName, $innerName);
177  } elseif ($reflection->getNumberOfRequiredParameters() > 1) {
178  throw new \Exception('A registered extension for the service "' . $serviceName . '" requires the service to be available, which is missing.', 1550092654);
179  }
180 
181  $staticallyCallable = $this->‪getStaticallyCallable($callable);
182  if ($staticallyCallable !== null) {
183  $factoryDefinition->setFactory($staticallyCallable);
184  $factoryDefinition->setArguments([
185  new Reference('service_container')
186  ]);
187  } else {
188  $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'extendService' ]);
189  $factoryDefinition->setArguments([
190  $serviceProviderKey,
191  $serviceName,
192  new Reference('service_container')
193  ]);
194  }
195 
196  if ($innerName !== null) {
197  $factoryDefinition->addArgument(new Reference($innerName));
198  }
199 
200  $container->setDefinition($finalServiceName, $factoryDefinition);
201  }
202 
207  private function ‪getStaticallyCallable(callable $callable): ?callable
208  {
209  if (is_string($callable)) {
210  return $callable;
211  }
212  if (is_array($callable) && isset($callable[0]) && is_string($callable[0])) {
213  return $callable;
214  }
215 
216  return null;
217  }
218 
224  private function ‪getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): ?string
225  {
226  if ($reflection->getReturnType() instanceof \ReflectionNamedType) {
227  return $reflection->getReturnType()->getName();
228  }
229 
230  if (class_exists($serviceName, true) || interface_exists($serviceName, true)) {
231  return $serviceName;
232  }
233 
234  return null;
235  }
236 
241  private function ‪getReflection(callable $callable): \ReflectionFunctionAbstract
242  {
243  if (is_array($callable) && count($callable) === 2) {
244  return new \ReflectionMethod($callable[0], $callable[1]);
245  }
246  if (is_object($callable) && !$callable instanceof \Closure) {
247  return new \ReflectionMethod($callable, '__invoke');
248  }
249 
250  return new \ReflectionFunction($callable);
251  }
252 
258  private function ‪getDecoratedServiceName(ContainerBuilder $container, string $serviceName): array
259  {
260  $counter = 1;
261  while ($container->has($serviceName . '_decorated_' . $counter)) {
262  $counter++;
263  }
264  return [
265  $serviceName . '_decorated_' . $counter,
266  $counter === 1 ? $serviceName : $serviceName . '_decorated_' . ($counter-1)
267  ];
268  }
269 }
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getStaticallyCallable
‪callable null getStaticallyCallable(callable $callable)
Definition: ServiceProviderCompilationPass.php:205
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\$registryServiceName
‪string $registryServiceName
Definition: ServiceProviderCompilationPass.php:36
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass
Definition: ServiceProviderCompilationPass.php:29
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getReturnType
‪string null getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName)
Definition: ServiceProviderCompilationPass.php:222
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\extendService
‪extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable)
Definition: ServiceProviderCompilationPass.php:157
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\process
‪process(ContainerBuilder $container)
Definition: ServiceProviderCompilationPass.php:53
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getDecoratedServiceName
‪array getDecoratedServiceName(ContainerBuilder $container, string $serviceName)
Definition: ServiceProviderCompilationPass.php:256
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerRegistry
‪registerRegistry(ContainerBuilder $container)
Definition: ServiceProviderCompilationPass.php:70
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerService
‪registerService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable)
Definition: ServiceProviderCompilationPass.php:111
‪TYPO3\CMS\Core\DependencyInjection
Definition: AutowireInjectMethodsPass.php:18
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderRegistry
Definition: ServiceProviderRegistry.php:31
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerExtensions
‪registerExtensions(ContainerBuilder $container, string $serviceProviderKey)
Definition: ServiceProviderCompilationPass.php:96
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\__construct
‪__construct(ServiceProviderRegistry $registry, string $registryServiceName='service_provider_registry')
Definition: ServiceProviderCompilationPass.php:42
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getReflection
‪ReflectionFunctionAbstract getReflection(callable $callable)
Definition: ServiceProviderCompilationPass.php:239
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerFactories
‪registerFactories(ContainerBuilder $container, string $serviceProviderKey)
Definition: ServiceProviderCompilationPass.php:83
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\$registry
‪ServiceProviderRegistry $registry
Definition: ServiceProviderCompilationPass.php:32