‪TYPO3CMS  10.4
Container.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 Doctrine\Instantiator\Instantiator;
21 use Doctrine\Instantiator\InstantiatorInterface;
22 use Psr\Container\ContainerInterface;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerAwareTrait;
32 
38 class ‪Container implements ‪SingletonInterface, LoggerAwareInterface
39 {
40  use LoggerAwareTrait;
41 
45  private ‪$psrContainer;
46 
54 
58  protected ‪$instantiator;
59 
65  private ‪$singletonInstances = [];
66 
73 
77  private ‪$reflectionService;
78 
82  public function ‪__construct(ContainerInterface ‪$psrContainer)
83  {
84  $this->psrContainer = ‪$psrContainer;
85  }
86 
92  protected function ‪getInstantiator(): InstantiatorInterface
93  {
94  if ($this->instantiator == null) {
95  $this->instantiator = new Instantiator();
96  }
98  }
99 
109  public function ‪getInstance(string $className, array $givenConstructorArguments = []): object
110  {
111  $this->prototypeObjectsWhichAreCurrentlyInstanciated = [];
112  return $this->‪getInstanceInternal($className, ...$givenConstructorArguments);
113  }
114 
121  public function ‪getEmptyObject(string $className): object
122  {
123  $className = $this->‪getImplementationClassName($className);
124  $classSchema = $this->‪getReflectionService()->‪getClassSchema($className);
125  $object = $this->‪getInstantiator()->instantiate($className);
126  $this->‪injectDependencies($object, $classSchema);
127  $this->‪initializeObject($object);
128  return $object;
129  }
130 
140  protected function ‪getInstanceInternal(string $className, ...$givenConstructorArguments): object
141  {
142  if ($className === ContainerInterface::class) {
143  return ‪$this->psrContainer;
144  }
145 
146  $className = $this->‪getImplementationClassName($className);
147 
148  if ($givenConstructorArguments === [] && $this->psrContainer->has($className)) {
149  $instance = $this->psrContainer->get($className);
150  if (!is_object($instance)) {
151  throw new Exception('PSR-11 container returned non object for class name "' . $className . '".', 1562240407);
152  }
153  return $instance;
154  }
155 
156  $className = ‪ClassLoadingInformation::getClassNameForAlias($className);
157  if (isset($this->singletonInstances[$className])) {
158  if (!empty($givenConstructorArguments)) {
159  throw new Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
160  }
161  return $this->singletonInstances[$className];
162  }
163 
164  $classSchema = $this->‪getReflectionService()->‪getClassSchema($className);
165  $classIsSingleton = $classSchema->‪isSingleton();
166  if (!$classIsSingleton) {
167  if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated) !== false) {
168  throw new CannotBuildObjectException('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
169  }
170  $this->prototypeObjectsWhichAreCurrentlyInstanciated[$className] = true;
171  }
172  $instance = $this->‪instanciateObject($classSchema, ...$givenConstructorArguments);
173  $this->‪injectDependencies($instance, $classSchema);
174  $this->‪initializeObject($instance);
175  if (!$classIsSingleton) {
176  unset($this->prototypeObjectsWhichAreCurrentlyInstanciated[$className]);
177  }
178  return $instance;
179  }
180 
191  protected function ‪instanciateObject(ClassSchema $classSchema, ...$givenConstructorArguments): object
192  {
193  $className = $classSchema->getClassName();
194  $classIsSingleton = $classSchema->isSingleton();
195  if ($classIsSingleton && !empty($givenConstructorArguments)) {
196  throw new Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
197  }
198  $constructorArguments = $this->‪getConstructorArguments($classSchema, $givenConstructorArguments);
199  $instance = GeneralUtility::makeInstance($className, ...$constructorArguments);
200  if ($classIsSingleton) {
201  $this->singletonInstances[$className] = $instance;
202  }
203  return $instance;
204  }
205 
212  protected function ‪injectDependencies(object $instance, ClassSchema $classSchema): void
213  {
214  if (!$classSchema->hasInjectMethods() && !$classSchema->hasInjectProperties()) {
215  return;
216  }
217  foreach ($classSchema->getInjectMethods() as $injectMethodName => $injectMethod) {
218  if (($classNameToInject = $injectMethod->getFirstParameter()->getDependency()) === null) {
219  continue;
220  }
221  $instanceToInject = $this->‪getInstanceInternal($classNameToInject);
222  if ($classSchema->isSingleton() && !$instanceToInject instanceof SingletonInterface) {
223  $this->logger->notice('The singleton "' . $classSchema->getClassName() . '" needs a prototype in "' . $injectMethodName . '". This is often a bad code smell; often you rather want to inject a singleton.');
224  }
225  if (is_callable([$instance, $injectMethodName])) {
226  $instance->{$injectMethodName}($instanceToInject);
227  }
228  }
229  foreach ($classSchema->getInjectProperties() as $injectPropertyName => $injectProperty) {
230  if (($classNameToInject = $injectProperty->getType()) === null) {
231  continue;
232  }
233 
234  $instanceToInject = $this->‪getInstanceInternal($classNameToInject);
235  if ($classSchema->isSingleton() && !$instanceToInject instanceof SingletonInterface) {
236  $this->logger->notice('The singleton "' . $classSchema->getClassName() . '" needs a prototype in "' . $injectPropertyName . '". This is often a bad code smell; often you rather want to inject a singleton.');
237  }
238 
239  if ($classSchema->getProperty($injectPropertyName)->isPublic()) {
240  $instance->{$injectPropertyName} = $instanceToInject;
241  } else {
242  $propertyReflection = new \ReflectionProperty($instance, $injectPropertyName);
243  $propertyReflection->setAccessible(true);
244  $propertyReflection->setValue($instance, $instanceToInject);
245  }
246  }
247  }
248 
254  protected function initializeObject(object $instance): void
255  {
256  if (method_exists($instance, 'initializeObject') && is_callable([$instance, 'initializeObject'])) {
257  $instance->initializeObject();
258  }
259  }
260 
269  public function registerImplementation(string $className, string $alternativeClassName): void
270  {
271  $this->alternativeImplementation[$className] = $alternativeClassName;
272  }
273 
282  private function getConstructorArguments(ClassSchema $classSchema, array $givenConstructorArguments): array
283  {
284  // @todo: -> private function getConstructorArguments(Method $constructor, array $givenConstructorArguments)
285 
286  if (!$classSchema->hasConstructor()) {
287  // todo: this check needs to take place outside this method
288  // todo: Instead of passing a ClassSchema object in here, all we need is a Method object instead
289  return [];
290  }
291 
292  $arguments = [];
293  foreach ($classSchema->getMethod('__construct')->getParameters() as $methodParameter) {
294  $index = $methodParameter->getPosition();
295 
296  // Constructor argument given AND argument is a simple type OR instance of argument type
297  if (array_key_exists($index, $givenConstructorArguments) &&
298  ($methodParameter->getDependency() === null || is_a($givenConstructorArguments[$index], $methodParameter->getDependency()))
299  ) {
300  $argument = $givenConstructorArguments[$index];
301  } else {
302  if ($methodParameter->getDependency() !== null && !$methodParameter->hasDefaultValue()) {
303  $argument = $this->getInstanceInternal($methodParameter->getDependency());
304  if ($classSchema->isSingleton() && !$argument instanceof SingletonInterface) {
305  $this->logger->notice('The singleton "' . $classSchema->getClassName() . '" needs a prototype in the constructor. This is often a bad code smell; often you rather want to inject a singleton.');
306  // todo: the whole injection is flawed anyway, why would we care about injecting prototypes? so, wayne?
307  // todo: btw: if this is important, we can already detect this case in the class schema.
308  }
309  } elseif ($methodParameter->hasDefaultValue() === true) {
310  $argument = $methodParameter->getDefaultValue();
311  } else {
312  throw new \InvalidArgumentException('not a correct info array of constructor dependencies was passed!', 1476107941);
313  }
314  }
315  $arguments[] = $argument;
316  }
317  return $arguments;
318  }
319 
327  public function getImplementationClassName(string $className): string
328  {
329  if (isset($this->alternativeImplementation[$className])) {
330  $className = $this->alternativeImplementation[$className];
331  }
332  if (substr($className, -9) === 'Interface') {
333  $className = substr($className, 0, -9);
334  }
335  return $className;
336  }
337 
346  protected function getReflectionService(): ReflectionService
347  {
348  return $this->reflectionService ?? ($this->reflectionService = $this->psrContainer->get(ReflectionService::class));
349  }
350 }
‪TYPO3\CMS\Extbase\Object\Container\Container\$reflectionService
‪ReflectionService $reflectionService
Definition: Container.php:71
‪TYPO3\CMS\Extbase\Object\Container\Container\getEmptyObject
‪object &T getEmptyObject(string $className)
Definition: Container.php:115
‪TYPO3\CMS\Extbase\Object\Container\Container\$singletonInstances
‪array $singletonInstances
Definition: Container.php:61
‪TYPO3\CMS\Extbase\Object\Container\Container
Definition: Container.php:39
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstanceInternal
‪object &T getInstanceInternal(string $className,... $givenConstructorArguments)
Definition: Container.php:134
‪TYPO3\CMS\Extbase\Object\Container\Container\getConstructorArguments
‪array getConstructorArguments(ClassSchema $classSchema, array $givenConstructorArguments)
Definition: Container.php:276
‪TYPO3\CMS\Extbase\Object\Container\Container\injectDependencies
‪injectDependencies(object $instance, ClassSchema $classSchema)
Definition: Container.php:206
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getMethod
‪Method getMethod(string $methodName)
Definition: ClassSchema.php:541
‪TYPO3\CMS\Extbase\Object\Container\Container\getImplementationClassName
‪string getImplementationClassName(string $className)
Definition: Container.php:321
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\isSingleton
‪bool isSingleton()
Definition: ClassSchema.php:610
‪TYPO3\CMS\Extbase\Object\Container
Definition: Container.php:18
‪TYPO3\CMS\Core\Core\ClassLoadingInformation
Definition: ClassLoadingInformation.php:35
‪TYPO3\CMS\Extbase\Object\Container\Container\getReflectionService
‪ReflectionService getReflectionService()
Definition: Container.php:340
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\hasInjectMethods
‪bool hasInjectMethods()
Definition: ClassSchema.php:635
‪TYPO3\CMS\Extbase\Object\Container\Container\$instantiator
‪InstantiatorInterface $instantiator
Definition: Container.php:55
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstance
‪object &T getInstance(string $className, array $givenConstructorArguments=[])
Definition: Container.php:103
‪TYPO3\CMS\Extbase\Reflection\ReflectionService\getClassSchema
‪ClassSchema getClassSchema($classNameOrObject)
Definition: ReflectionService.php:98
‪TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException
Definition: CannotBuildObjectException.php:26
‪TYPO3\CMS\Extbase\Object\Container\Container\initializeObject
‪initializeObject(object $instance)
Definition: Container.php:248
‪TYPO3\CMS\Extbase\Object\Container\Container\__construct
‪__construct(ContainerInterface $psrContainer)
Definition: Container.php:76
‪TYPO3\CMS\Extbase\Reflection\ReflectionService
Definition: ReflectionService.php:31
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static mixed getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:207
‪TYPO3\CMS\Extbase\Reflection\ClassSchema
‪TYPO3\CMS\Extbase\Object\Container\Container\$prototypeObjectsWhichAreCurrentlyInstanciated
‪array $prototypeObjectsWhichAreCurrentlyInstanciated
Definition: Container.php:67
‪TYPO3\CMS\Extbase\Object\Container\Container\instanciateObject
‪object instanciateObject(ClassSchema $classSchema,... $givenConstructorArguments)
Definition: Container.php:185
‪TYPO3\CMS\Extbase\Object\Exception
Definition: CannotBuildObjectException.php:18
‪TYPO3\CMS\Extbase\Object\Container\Container\$alternativeImplementation
‪array $alternativeImplementation
Definition: Container.php:51
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\Method\getParameters
‪array MethodParameter[] getParameters()
Definition: Method.php:87
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\hasConstructor
‪bool hasConstructor()
Definition: ClassSchema.php:530
‪TYPO3\CMS\Extbase\Object\Exception
Definition: Exception.php:26
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getInjectMethods
‪array Method[] getInjectMethods()
Definition: ClassSchema.php:643
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\hasInjectProperties
‪bool hasInjectProperties()
Definition: ClassSchema.php:627
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstantiator
‪InstantiatorInterface getInstantiator()
Definition: Container.php:86
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getClassName
‪string getClassName()
Definition: ClassSchema.php:475
‪TYPO3\CMS\Extbase\Object\Container\Container\registerImplementation
‪registerImplementation(string $className, string $alternativeClassName)
Definition: Container.php:263
‪TYPO3\CMS\Extbase\Object\Container\Container\$psrContainer
‪ContainerInterface $psrContainer
Definition: Container.php:44
‪TYPO3\CMS\Extbase\Reflection\ClassSchema
Definition: ClassSchema.php:55
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46