‪TYPO3CMS  ‪main
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 
40  public function ‪__construct(‪ServiceProviderRegistry ‪$registry, string ‪$registryServiceName = 'service_provider_registry')
41  {
42  $this->registry = ‪$registry;
43  $this->registryServiceName = ‪$registryServiceName;
44  }
45 
51  public function ‪process(ContainerBuilder $container): void
52  {
53  // Now, let's store the registry in the container (an empty version of it... it has to be added dynamically at runtime):
54  $this->‪registerRegistry($container);
55 
56  foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
57  $this->‪registerFactories($container, $serviceProviderKey);
58  }
59 
60  foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
61  $this->‪registerExtensions($container, $serviceProviderKey);
62  }
63  }
64 
68  private function ‪registerRegistry(ContainerBuilder $container): void
69  {
70  $definition = new Definition(ServiceProviderRegistry::class);
71  $definition->setSynthetic(true);
72  $definition->setPublic(true);
73 
74  $container->setDefinition($this->registryServiceName, $definition);
75  }
76 
80  private function ‪registerFactories(ContainerBuilder $container, string $serviceProviderKey): void
81  {
82  $serviceFactories = $this->registry->getFactories($serviceProviderKey);
83 
84  foreach ($serviceFactories as $serviceName => $callable) {
85  $this->‪registerService($container, $serviceName, $serviceProviderKey, $callable);
86  }
87  }
88 
92  private function ‪registerExtensions(ContainerBuilder $container, string $serviceProviderKey): void
93  {
94  $serviceExtensions = $this->registry->getExtensions($serviceProviderKey);
95 
96  foreach ($serviceExtensions as $serviceName => $callable) {
97  $this->‪extendService($container, $serviceName, $serviceProviderKey, $callable);
98  }
99  }
100 
104  private function ‪registerService(
105  ContainerBuilder $container,
106  string $serviceName,
107  string $serviceProviderKey,
108  callable $callable
109  ): void {
110  if (!$container->hasDefinition($serviceName)) {
111  // Create a new definition
112  $factoryDefinition = new Definition();
113  $container->setDefinition($serviceName, $factoryDefinition);
114  } else {
115  // Merge into an existing definition to keep possible addMethodCall/properties configurations
116  // (which act like a service extension)
117  // Retrieve the existing factory and overwrite it.
118  $factoryDefinition = $container->getDefinition($serviceName);
119  if ($factoryDefinition->isAutowired()) {
120  $factoryDefinition->setAutowired(false);
121  }
122  }
123 
124  $className = $this->‪getReturnType($this->‪getReflection($callable), $serviceName) ?? 'object';
125  $factoryDefinition->setClass($className);
126  $factoryDefinition->setPublic(true);
127 
128  $staticallyCallable = $this->‪getStaticallyCallable($callable);
129  if ($staticallyCallable !== null) {
130  $factoryDefinition->setFactory($staticallyCallable);
131  $factoryDefinition->setArguments([
132  new Reference('service_container'),
133  ]);
134  } else {
135  $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'createService' ]);
136  $factoryDefinition->setArguments([
137  $serviceProviderKey,
138  $serviceName,
139  new Reference('service_container'),
140  ]);
141  }
142  }
143 
147  private function ‪extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable): void
148  {
149  $finalServiceName = $serviceName;
150  $innerName = null;
151 
152  $reflection = $this->‪getReflection($callable);
153  $previousClass = $container->hasDefinition($serviceName) ? $container->getDefinition($serviceName)->getClass() : null;
154  $className = $this->‪getReturnType($reflection, $serviceName) ?? $previousClass ?? 'object';
155 
156  $factoryDefinition = new Definition($className);
157  $factoryDefinition->setClass($className);
158  $factoryDefinition->setPublic(true);
159 
160  if ($container->has($serviceName)) {
161  [$finalServiceName, $previousServiceName] = $this->‪getDecoratedServiceName($container, $serviceName);
162  $innerName = $finalServiceName . '.inner';
163 
164  $factoryDefinition->setDecoratedService($previousServiceName, $innerName);
165  } elseif ($reflection->getNumberOfRequiredParameters() > 1) {
166  throw new \Exception('A registered extension for the service "' . $serviceName . '" requires the service to be available, which is missing.', 1550092654);
167  }
168 
169  $staticallyCallable = $this->‪getStaticallyCallable($callable);
170  if ($staticallyCallable !== null) {
171  $factoryDefinition->setFactory($staticallyCallable);
172  $factoryDefinition->setArguments([
173  new Reference('service_container'),
174  ]);
175  } else {
176  $factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'extendService' ]);
177  $factoryDefinition->setArguments([
178  $serviceProviderKey,
179  $serviceName,
180  new Reference('service_container'),
181  ]);
182  }
183 
184  if ($innerName !== null) {
185  $factoryDefinition->addArgument(new Reference($innerName));
186  }
187 
188  $container->setDefinition($finalServiceName, $factoryDefinition);
189  }
190 
191  private function ‪getStaticallyCallable(callable $callable): ?callable
192  {
193  if (is_string($callable)) {
194  return $callable;
195  }
196  if (is_array($callable) && isset($callable[0]) && is_string($callable[0])) {
197  return $callable;
198  }
199 
200  if ($callable instanceof \Closure) {
201  $reflection = new \ReflectionFunction($callable);
202  $reflectionScope = $reflection->getClosureScopeClass();
203  $usedVariables = count($reflection->getClosureUsedVariables());
204 
205  if ($reflection->isStatic() && !$reflection->isAnonymous() && $reflectionScope !== null && $usedVariables === 0) {
206  return [
207  $reflectionScope->getName(),
208  $reflection->getName(),
209  ];
210  }
211  }
212 
213  return null;
214  }
215 
216  private function ‪getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): ?string
217  {
218  if ($reflection->getReturnType() instanceof \ReflectionNamedType) {
219  return $reflection->getReturnType()->getName();
220  }
221 
222  if (class_exists($serviceName, true) || interface_exists($serviceName, true)) {
223  return $serviceName;
224  }
225 
226  return null;
227  }
228 
229  private function ‪getReflection(callable $callable): \ReflectionFunctionAbstract
230  {
231  if (is_array($callable) && count($callable) === 2) {
232  return new \ReflectionMethod($callable[0], $callable[1]);
233  }
234  if (is_object($callable) && !$callable instanceof \Closure) {
235  return new \ReflectionMethod($callable, '__invoke');
236  }
237 
238  return new \ReflectionFunction($callable);
239  }
240 
244  private function ‪getDecoratedServiceName(ContainerBuilder $container, string $serviceName): array
245  {
246  $counter = 1;
247  while ($container->has($serviceName . '_decorated_' . $counter)) {
248  $counter++;
249  }
250  return [
251  $serviceName . '_decorated_' . $counter,
252  $counter === 1 ? $serviceName : $serviceName . '_decorated_' . ($counter - 1),
253  ];
254  }
255 }
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getStaticallyCallable
‪getStaticallyCallable(callable $callable)
Definition: ServiceProviderCompilationPass.php:189
‪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\extendService
‪extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable)
Definition: ServiceProviderCompilationPass.php:145
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\process
‪process(ContainerBuilder $container)
Definition: ServiceProviderCompilationPass.php:49
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getReflection
‪getReflection(callable $callable)
Definition: ServiceProviderCompilationPass.php:227
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getReturnType
‪getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName)
Definition: ServiceProviderCompilationPass.php:214
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerRegistry
‪registerRegistry(ContainerBuilder $container)
Definition: ServiceProviderCompilationPass.php:66
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\getDecoratedServiceName
‪getDecoratedServiceName(ContainerBuilder $container, string $serviceName)
Definition: ServiceProviderCompilationPass.php:242
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerService
‪registerService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable)
Definition: ServiceProviderCompilationPass.php:102
‪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:90
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\__construct
‪__construct(ServiceProviderRegistry $registry, string $registryServiceName='service_provider_registry')
Definition: ServiceProviderCompilationPass.php:38
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\registerFactories
‪registerFactories(ContainerBuilder $container, string $serviceProviderKey)
Definition: ServiceProviderCompilationPass.php:78
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass\$registry
‪ServiceProviderRegistry $registry
Definition: ServiceProviderCompilationPass.php:32