2 namespace TYPO3\CMS\Extbase\
Object\Container
;
5 * This file is part of the TYPO3 CMS project.
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use Psr\Log\LoggerInterface
;
18 use TYPO3\CMS\Core\Cache\CacheManager
;
19 use TYPO3\CMS\Core\Log\LogManager
;
20 use TYPO3\CMS\Core\Utility\GeneralUtility
;
21 use TYPO3\CMS\Extbase\Reflection\ClassSchema
;
22 use TYPO3\CMS\Extbase\Reflection\ReflectionService
;
25 * Internal TYPO3 Dependency Injection container
27 class Container
implements \TYPO3\CMS\Core\SingletonInterface
29 const SCOPE_PROTOTYPE
= 1;
30 const SCOPE_SINGLETON
= 2;
33 * registered alternative implementations of a class
34 * e.g. used to know the class for an AbstractClass or a Dependency
38 private $alternativeImplementation;
41 * @var \Doctrine\Instantiator\InstantiatorInterface
43 protected $instantiator = null;
46 * holds references of singletons
50 private $singletonInstances = [];
53 * Array of prototype objects currently being built, to prevent recursion.
57 private $prototypeObjectsWhichAreCurrentlyInstanciated;
60 * @var ReflectionService
62 private $reflectionService;
65 * Constructor is protected since container should
70 public function __construct()
72 $this->reflectionService
= GeneralUtility
::makeInstance(ReflectionService
::class, GeneralUtility
::makeInstance(CacheManager
::class));
76 * Internal method to create the class instantiator, extracted to be mockable
78 * @return \Doctrine\Instantiator\InstantiatorInterface
80 protected function getInstantiator()
82 if ($this->instantiator
== null) {
83 $this->instantiator
= new \Doctrine\Instantiator\
Instantiator();
85 return $this->instantiator
;
89 * Main method which should be used to get an instance of the wished class
90 * specified by $className.
92 * @param string $className
93 * @param array $givenConstructorArguments the list of constructor arguments as array
94 * @return object the built object
96 public function getInstance($className, $givenConstructorArguments = [])
98 $this->prototypeObjectsWhichAreCurrentlyInstanciated
= [];
99 return $this->getInstanceInternal($className, $givenConstructorArguments);
103 * Create an instance of $className without calling its constructor
105 * @param string $className
108 public function getEmptyObject($className)
110 $className = $this->getImplementationClassName($className);
111 $classSchema = $this->reflectionService
->getClassSchema($className);
112 $object = $this->getInstantiator()->instantiate($className);
113 $this->injectDependencies($object, $classSchema);
114 $this->initializeObject($object);
119 * Internal implementation for getting a class.
121 * @param string $className
122 * @param array $givenConstructorArguments the list of constructor arguments as array
123 * @throws \TYPO3\CMS\Extbase\Object\Exception
124 * @throws \TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException
125 * @return object the built object
127 protected function getInstanceInternal($className, $givenConstructorArguments = [])
129 $className = $this->getImplementationClassName($className);
130 if ($className === \TYPO3\CMS\Extbase\
Object\Container\Container
::class) {
133 if ($className === \TYPO3\CMS\Core\Cache\CacheManager
::class) {
134 return GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager
::class);
136 if ($className === \TYPO3\CMS\Core\Package\PackageManager
::class) {
137 return GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Package\PackageManager
::class);
139 $className = \TYPO3\CMS\Core\Core\ClassLoadingInformation
::getClassNameForAlias($className);
140 if (isset($this->singletonInstances
[$className])) {
141 if (!empty($givenConstructorArguments)) {
142 throw new \TYPO3\CMS\Extbase\
Object\
Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
144 return $this->singletonInstances
[$className];
147 $classSchema = $this->reflectionService
->getClassSchema($className);
148 $classIsSingleton = $classSchema->isSingleton();
149 if (!$classIsSingleton) {
150 if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated
) !== false) {
151 throw new \TYPO3\CMS\Extbase\
Object\Exception\
CannotBuildObjectException('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
153 $this->prototypeObjectsWhichAreCurrentlyInstanciated
[$className] = true;
155 $instance = $this->instanciateObject($classSchema, $givenConstructorArguments);
156 $this->injectDependencies($instance, $classSchema);
157 $this->initializeObject($instance);
158 if (!$classIsSingleton) {
159 unset($this->prototypeObjectsWhichAreCurrentlyInstanciated
[$className]);
165 * Instanciates an object, possibly setting the constructor dependencies.
166 * Additionally, directly registers all singletons in the singleton registry,
167 * such that circular references of singletons are correctly instanciated.
169 * @param ClassSchema $classSchema
170 * @param array $givenConstructorArguments
171 * @throws \TYPO3\CMS\Extbase\Object\Exception
172 * @return object the new instance
174 protected function instanciateObject(ClassSchema
$classSchema, array $givenConstructorArguments)
176 $className = $classSchema->getClassName();
177 $classIsSingleton = $classSchema->isSingleton();
178 if ($classIsSingleton && !empty($givenConstructorArguments)) {
179 throw new \TYPO3\CMS\Extbase\
Object\
Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
181 $constructorArguments = $this->getConstructorArguments($className, $classSchema, $givenConstructorArguments);
182 array_unshift($constructorArguments, $className);
183 $instance = call_user_func_array([GeneralUtility
::class, 'makeInstance'], $constructorArguments);
184 if ($classIsSingleton) {
185 $this->singletonInstances
[$className] = $instance;
191 * Inject setter-dependencies into $instance
193 * @param object $instance
194 * @param ClassSchema $classSchema
196 protected function injectDependencies($instance, ClassSchema
$classSchema)
198 if (!$classSchema->hasInjectMethods() && !$classSchema->hasInjectProperties()) {
201 foreach ($classSchema->getInjectMethods() as $injectMethodName => $classNameToInject) {
202 $instanceToInject = $this->getInstanceInternal($classNameToInject);
203 if ($classSchema->isSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface
) {
204 $this->getLogger()->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.');
206 if (is_callable([$instance, $injectMethodName])) {
207 $instance->{$injectMethodName}($instanceToInject);
210 foreach ($classSchema->getInjectProperties() as $injectPropertyName => $classNameToInject) {
211 $instanceToInject = $this->getInstanceInternal($classNameToInject);
212 if ($classSchema->isSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface
) {
213 $this->getLogger()->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.');
215 $propertyReflection = new \
ReflectionProperty($instance, $injectPropertyName);
217 $propertyReflection->setAccessible(true);
218 $propertyReflection->setValue($instance, $instanceToInject);
223 * Call object initializer if present in object
225 * @param object $instance
227 protected function initializeObject($instance)
229 if (method_exists($instance, 'initializeObject') && is_callable([$instance, 'initializeObject'])) {
230 $instance->initializeObject();
235 * register a classname that should be used if a dependency is required.
236 * e.g. used to define default class for an interface
238 * @param string $className
239 * @param string $alternativeClassName
241 public function registerImplementation($className, $alternativeClassName)
243 $this->alternativeImplementation
[$className] = $alternativeClassName;
247 * gets array of parameter that can be used to call a constructor
249 * @param string $className
250 * @param ClassSchema $classSchema
251 * @param array $givenConstructorArguments
252 * @throws \InvalidArgumentException
255 private function getConstructorArguments($className, ClassSchema
$classSchema, array $givenConstructorArguments)
258 $constructorArgumentInformation = $classSchema->getConstructorArguments();
259 foreach ($constructorArgumentInformation as $constructorArgumentName => $argumentInformation) {
260 $index = $argumentInformation['position'];
262 // Constructor argument given AND argument is a simple type OR instance of argument type
263 if (array_key_exists($index, $givenConstructorArguments) && (!isset($argumentInformation['dependency']) ||
is_a($givenConstructorArguments[$index], $argumentInformation['dependency']))) {
264 $parameter = $givenConstructorArguments[$index];
266 if (isset($argumentInformation['dependency']) && $argumentInformation['hasDefaultValue'] === false) {
267 $parameter = $this->getInstanceInternal($argumentInformation['dependency']);
268 if ($classSchema->isSingleton() && !$parameter instanceof \TYPO3\CMS\Core\SingletonInterface
) {
269 $this->getLogger()->notice('The singleton "' . $className . '" needs a prototype in the constructor. This is often a bad code smell; often you rather want to inject a singleton.');
271 } elseif ($argumentInformation['hasDefaultValue'] === true) {
272 $parameter = $argumentInformation['defaultValue'];
274 throw new \
InvalidArgumentException('not a correct info array of constructor dependencies was passed!', 1476107941);
277 $parameters[] = $parameter;
283 * Returns the class name for a new instance, taking into account the
284 * class-extension API.
286 * @param string $className Base class name to evaluate
287 * @return string Final class name to instantiate with "new [classname]
289 public function getImplementationClassName($className)
291 if (isset($this->alternativeImplementation
[$className])) {
292 $className = $this->alternativeImplementation
[$className];
294 if (substr($className, -9) === 'Interface') {
295 $className = substr($className, 0, -9);
301 * @param string $className
305 public function isSingleton($className)
307 return $this->reflectionService
->getClassSchema($className)->isSingleton();
311 * @param string $className
315 public function isPrototype($className)
317 return !$this->isSingleton($className);
321 * @return LoggerInterface
323 protected function getLogger()
325 return GeneralUtility
::makeInstance(LogManager
::class)->getLogger(static::class);