‪TYPO3CMS  11.5
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 
41 class ‪Container implements ‪SingletonInterface, LoggerAwareInterface
42 {
43  use LoggerAwareTrait;
44 
48  private ‪$psrContainer;
49 
57 
61  protected ‪$instantiator;
62 
68  private ‪$singletonInstances = [];
69 
76 
80  private ‪$reflectionService;
81 
85  public function ‪__construct(ContainerInterface ‪$psrContainer)
86  {
87  $this->psrContainer = ‪$psrContainer;
88  }
89 
95  protected function ‪getInstantiator(): InstantiatorInterface
96  {
97  if ($this->instantiator == null) {
98  $this->instantiator = new Instantiator();
99  }
100  return ‪$this->instantiator;
101  }
102 
112  public function ‪getInstance(string $className, array $givenConstructorArguments = []): object
113  {
114  $this->prototypeObjectsWhichAreCurrentlyInstanciated = [];
115  return $this->‪getInstanceInternal($className, ...$givenConstructorArguments);
116  }
117 
124  public function ‪getEmptyObject(string $className): object
125  {
126  $className = $this->‪getImplementationClassName($className);
127  $classSchema = $this->‪getReflectionService()->‪getClassSchema($className);
128  $object = $this->‪getInstantiator()->instantiate($className);
129  $this->‪injectDependencies($object, $classSchema);
130  $this->‪initializeObject($object);
131  return $object;
132  }
133 
143  protected function ‪getInstanceInternal(string $className, ...$givenConstructorArguments): object
144  {
145  if ($className === ContainerInterface::class) {
146  return ‪$this->psrContainer;
147  }
148 
149  $className = $this->‪getImplementationClassName($className);
150 
151  if ($givenConstructorArguments === [] && $this->psrContainer->has($className)) {
152  $instance = $this->psrContainer->get($className);
153  if (!is_object($instance)) {
154  throw new Exception('PSR-11 container returned non object for class name "' . $className . '".', 1562240407);
155  }
156  return $instance;
157  }
158 
159  $className = ‪ClassLoadingInformation::getClassNameForAlias($className);
160  if (isset($this->singletonInstances[$className])) {
161  if (!empty($givenConstructorArguments)) {
162  throw new Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
163  }
164  return $this->singletonInstances[$className];
165  }
166 
167  $classSchema = $this->‪getReflectionService()->‪getClassSchema($className);
168  $classIsSingleton = $classSchema->‪isSingleton();
169  if (!$classIsSingleton) {
170  if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated) !== false) {
171  throw new CannotBuildObjectException('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
172  }
173  $this->prototypeObjectsWhichAreCurrentlyInstanciated[$className] = true;
174  }
175  $instance = $this->‪instanciateObject($classSchema, ...$givenConstructorArguments);
176  $this->‪injectDependencies($instance, $classSchema);
177  $this->‪initializeObject($instance);
178  if (!$classIsSingleton) {
179  unset($this->prototypeObjectsWhichAreCurrentlyInstanciated[$className]);
180  }
181  return $instance;
182  }
183 
194  protected function ‪instanciateObject(ClassSchema $classSchema, ...$givenConstructorArguments): object
195  {
196  $className = $classSchema->getClassName();
197  $classIsSingleton = $classSchema->isSingleton();
198  if ($classIsSingleton && !empty($givenConstructorArguments)) {
199  throw new Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
200  }
201  $constructorArguments = $this->‪getConstructorArguments($classSchema, $givenConstructorArguments);
202  $instance = GeneralUtility::makeInstance($className, ...$constructorArguments);
203  if ($classIsSingleton) {
204  $this->singletonInstances[$className] = $instance;
205  }
206  return $instance;
207  }
208 
215  protected function ‪injectDependencies(object $instance, ClassSchema $classSchema): void
216  {
217  if (!$classSchema->hasInjectMethods() && !$classSchema->hasInjectProperties()) {
218  return;
219  }
220  foreach ($classSchema->getInjectMethods() as $injectMethodName => $injectMethod) {
221  if (($classNameToInject = $injectMethod->getFirstParameter()->getDependency()) === null) {
222  continue;
223  }
224  $instanceToInject = $this->‪getInstanceInternal($classNameToInject);
225  if ($classSchema->isSingleton() && !$instanceToInject instanceof SingletonInterface) {
226  $this->logger->notice('The singleton "{class}" needs a prototype in "{method}". This is often a bad code smell; often you rather want to inject a singleton.', [
227  'class' => $classSchema->getClassName(),
228  'method' => $injectMethodName,
229  ]);
230  }
231  if (is_callable([$instance, $injectMethodName])) {
232  $instance->{$injectMethodName}($instanceToInject);
233  }
234  }
235  foreach ($classSchema->getInjectProperties() as $injectPropertyName => $injectProperty) {
236  if (($classNameToInject = $injectProperty->getType()) === null) {
237  continue;
238  }
239 
240  $instanceToInject = $this->‪getInstanceInternal($classNameToInject);
241  if ($classSchema->isSingleton() && !$instanceToInject instanceof SingletonInterface) {
242  $this->logger->notice('The singleton "{class}" needs a prototype in "{method}". This is often a bad code smell; often you rather want to inject a singleton.', [
243  'class' => $classSchema->getClassName(),
244  'method' => $injectPropertyName,
245  ]);
246  }
247 
248  $instance->{$injectPropertyName} = $instanceToInject;
249  }
250  }
251 
257  protected function initializeObject(object $instance): void
258  {
259  if (method_exists($instance, 'initializeObject') && is_callable([$instance, 'initializeObject'])) {
260  $instance->initializeObject();
261  }
262  }
263 
272  public function registerImplementation(string $className, string $alternativeClassName): void
273  {
274  $this->alternativeImplementation[$className] = $alternativeClassName;
275  }
276 
285  private function getConstructorArguments(ClassSchema $classSchema, array $givenConstructorArguments): array
286  {
287  // @todo: -> private function getConstructorArguments(Method $constructor, array $givenConstructorArguments)
288 
289  if (!$classSchema->hasConstructor()) {
290  // todo: this check needs to take place outside this method
291  // todo: Instead of passing a ClassSchema object in here, all we need is a Method object instead
292  return [];
293  }
294 
295  $arguments = [];
296  foreach ($classSchema->getMethod('__construct')->getParameters() as $methodParameter) {
297  $index = $methodParameter->getPosition();
298 
299  // Constructor argument given AND argument is a simple type OR instance of argument type
300  if (array_key_exists($index, $givenConstructorArguments) &&
301  ($methodParameter->getDependency() === null || is_a($givenConstructorArguments[$index], $methodParameter->getDependency()))
302  ) {
303  $argument = $givenConstructorArguments[$index];
304  } else {
305  if ($methodParameter->getDependency() !== null && !$methodParameter->hasDefaultValue()) {
306  $argument = $this->getInstanceInternal($methodParameter->getDependency());
307  if ($classSchema->isSingleton() && !$argument instanceof SingletonInterface) {
308  $this->logger->notice('The singleton "{class_name}" needs a prototype in the constructor. This is often a bad code smell; often you rather want to inject a singleton.', ['class_name' => $classSchema->getClassName()]);
309  // todo: the whole injection is flawed anyway, why would we care about injecting prototypes? so, wayne?
310  // todo: btw: if this is important, we can already detect this case in the class schema.
311  }
312  } elseif ($methodParameter->hasDefaultValue() === true) {
313  $argument = $methodParameter->getDefaultValue();
314  } else {
315  throw new \InvalidArgumentException('not a correct info array of constructor dependencies was passed!', 1476107941);
316  }
317  }
318  $arguments[] = $argument;
319  }
320  return $arguments;
321  }
322 
330  public function getImplementationClassName(string $className): string
331  {
332  if (isset($this->alternativeImplementation[$className])) {
333  $className = $this->alternativeImplementation[$className];
334  }
335  if (substr($className, -9) === 'Interface') {
336  $className = substr($className, 0, -9);
337  }
338  return $className;
339  }
340 
349  protected function getReflectionService(): ReflectionService
350  {
351  return $this->reflectionService ?? ($this->reflectionService = $this->psrContainer->get(ReflectionService::class));
352  }
353 }
‪TYPO3\CMS\Extbase\Object\Container\Container\$reflectionService
‪ReflectionService $reflectionService
Definition: Container.php:74
‪TYPO3\CMS\Extbase\Object\Container\Container\getEmptyObject
‪object &T getEmptyObject(string $className)
Definition: Container.php:118
‪TYPO3\CMS\Extbase\Object\Container\Container\$singletonInstances
‪array $singletonInstances
Definition: Container.php:64
‪TYPO3\CMS\Extbase\Object\Container\Container
Definition: Container.php:42
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstanceInternal
‪object &T getInstanceInternal(string $className,... $givenConstructorArguments)
Definition: Container.php:137
‪TYPO3\CMS\Extbase\Object\Container\Container\getConstructorArguments
‪array getConstructorArguments(ClassSchema $classSchema, array $givenConstructorArguments)
Definition: Container.php:279
‪TYPO3\CMS\Extbase\Object\Container\Container\injectDependencies
‪injectDependencies(object $instance, ClassSchema $classSchema)
Definition: Container.php:209
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getMethod
‪Method getMethod(string $methodName)
Definition: ClassSchema.php:579
‪TYPO3\CMS\Extbase\Object\Container\Container\getImplementationClassName
‪string getImplementationClassName(string $className)
Definition: Container.php:324
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\isSingleton
‪bool isSingleton()
Definition: ClassSchema.php:648
‪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:343
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\hasInjectMethods
‪bool hasInjectMethods()
Definition: ClassSchema.php:673
‪TYPO3\CMS\Extbase\Object\Container\Container\$instantiator
‪InstantiatorInterface $instantiator
Definition: Container.php:58
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstance
‪object &T getInstance(string $className, array $givenConstructorArguments=[])
Definition: Container.php:106
‪TYPO3\CMS\Extbase\Reflection\ReflectionService\getClassSchema
‪ClassSchema getClassSchema($classNameOrObject)
Definition: ReflectionService.php:76
‪TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException
Definition: CannotBuildObjectException.php:27
‪TYPO3\CMS\Extbase\Object\Container\Container\initializeObject
‪initializeObject(object $instance)
Definition: Container.php:251
‪TYPO3\CMS\Extbase\Object\Container\Container\__construct
‪__construct(ContainerInterface $psrContainer)
Definition: Container.php:79
‪TYPO3\CMS\Extbase\Reflection\ReflectionService
Definition: ReflectionService.php:28
‪TYPO3\CMS\Core\Core\ClassLoadingInformation\getClassNameForAlias
‪static mixed getClassNameForAlias($alias)
Definition: ClassLoadingInformation.php:205
‪TYPO3\CMS\Extbase\Reflection\ClassSchema
‪TYPO3\CMS\Extbase\Object\Container\Container\$prototypeObjectsWhichAreCurrentlyInstanciated
‪array $prototypeObjectsWhichAreCurrentlyInstanciated
Definition: Container.php:70
‪TYPO3\CMS\Extbase\Object\Container\Container\instanciateObject
‪object instanciateObject(ClassSchema $classSchema,... $givenConstructorArguments)
Definition: Container.php:188
‪TYPO3\CMS\Extbase\Object\Exception
Definition: CannotBuildObjectException.php:18
‪TYPO3\CMS\Extbase\Object\Container\Container\$alternativeImplementation
‪array $alternativeImplementation
Definition: Container.php:54
‪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:568
‪TYPO3\CMS\Extbase\Object\Exception
Definition: Exception.php:27
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getInjectMethods
‪array Method[] getInjectMethods()
Definition: ClassSchema.php:681
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\hasInjectProperties
‪bool hasInjectProperties()
Definition: ClassSchema.php:665
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Extbase\Object\Container\Container\getInstantiator
‪InstantiatorInterface getInstantiator()
Definition: Container.php:89
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\getClassName
‪string getClassName()
Definition: ClassSchema.php:499
‪TYPO3\CMS\Extbase\Object\Container\Container\registerImplementation
‪registerImplementation(string $className, string $alternativeClassName)
Definition: Container.php:266
‪TYPO3\CMS\Extbase\Object\Container\Container\$psrContainer
‪ContainerInterface $psrContainer
Definition: Container.php:47
‪TYPO3\CMS\Extbase\Reflection\ClassSchema
Definition: ClassSchema.php:54
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50