2 namespace TYPO3\CMS\Extbase\
Object\Container
;
4 /***************************************************************
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
31 * Internal TYPO3 Dependency Injection container
33 * @author Daniel Pötzinger
34 * @author Sebastian Kurfürst
35 * @author Timo Schmidt
37 class Container
implements \TYPO3\CMS\Core\SingletonInterface
{
39 const SCOPE_PROTOTYPE
= 1;
40 const SCOPE_SINGLETON
= 2;
43 * internal cache for classinfos
45 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
47 private $cache = NULL;
50 * registered alternative implementations of a class
51 * e.g. used to know the class for a AbstractClass or a Dependency
55 private $alternativeImplementation;
58 * reference to the classinfofactory, that analyses dependencys
60 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
62 private $classInfoFactory = NULL;
65 * holds references of singletons
69 private $singletonInstances = array();
72 * Array of prototype objects currently being built, to prevent recursion.
76 private $prototypeObjectsWhichAreCurrentlyInstanciated;
79 * Constructor is protected since container should
84 public function __construct() {
88 * Internal method to create the classInfoFactory, extracted to be mockable.
90 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
92 protected function getClassInfoFactory() {
93 if ($this->classInfoFactory
== NULL) {
94 $this->classInfoFactory
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoFactory');
96 return $this->classInfoFactory
;
100 * Internal method to create the classInfoCache, extracted to be mockable.
102 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
104 protected function getClassInfoCache() {
105 if ($this->cache
== NULL) {
106 $this->cache
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoCache');
112 * Main method which should be used to get an instance of the wished class
113 * specified by $className.
115 * @param string $className
116 * @param array $givenConstructorArguments the list of constructor arguments as array
117 * @return object the built object
119 public function getInstance($className, $givenConstructorArguments = array()) {
120 $this->prototypeObjectsWhichAreCurrentlyInstanciated
= array();
121 return $this->getInstanceInternal($className, $givenConstructorArguments);
125 * Create an instance of $className without calling its constructor
127 * @param string $className
130 public function getEmptyObject($className) {
131 $className = $this->getImplementationClassName($className);
132 $classInfo = $this->getClassInfo($className);
133 // get an object and avoid calling __construct()
134 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
135 $this->injectDependencies($object, $classInfo);
140 * Internal implementation for getting a class.
142 * @param string $className
143 * @param array $givenConstructorArguments the list of constructor arguments as array
144 * @throws \TYPO3\CMS\Extbase\Object\Exception
145 * @throws \TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException
146 * @return object the built object
148 protected function getInstanceInternal($className, $givenConstructorArguments = array()) {
149 $className = $this->getImplementationClassName($className);
150 if ($className === 'TYPO3\\CMS\\Extbase\\Object\\Container\\Container') {
153 if ($className === 'TYPO3\\CMS\\Core\\Cache\\CacheManager') {
154 return $GLOBALS['typo3CacheManager'];
156 $className = \TYPO3\CMS\Core\Core\ClassLoader
::getClassNameForAlias($className);
157 if (isset($this->singletonInstances
[$className])) {
158 if (count($givenConstructorArguments) > 0) {
159 throw new \TYPO3\CMS\Extbase\
Object\
Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
161 return $this->singletonInstances
[$className];
163 $classInfo = $this->getClassInfo($className);
164 $classIsSingleton = $classInfo->getIsSingleton();
165 if (!$classIsSingleton) {
166 if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated
) !== FALSE) {
167 throw new \TYPO3\CMS\Extbase\
Object\Exception\
CannotBuildObjectException('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
169 $this->prototypeObjectsWhichAreCurrentlyInstanciated
[$className] = TRUE;
171 $instance = $this->instanciateObject($classInfo, $givenConstructorArguments);
172 $this->injectDependencies($instance, $classInfo);
173 if ($classInfo->getIsInitializeable() && is_callable(array($instance, 'initializeObject'))) {
174 $instance->initializeObject();
176 if (!$classIsSingleton) {
177 unset($this->prototypeObjectsWhichAreCurrentlyInstanciated
[$className]);
183 * Instanciates an object, possibly setting the constructor dependencies.
184 * Additionally, directly registers all singletons in the singleton registry,
185 * such that circular references of singletons are correctly instanciated.
187 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
188 * @param array $givenConstructorArguments
189 * @throws \TYPO3\CMS\Extbase\Object\Exception
190 * @return object the new instance
192 protected function instanciateObject(\TYPO3\CMS\Extbase\
Object\Container\ClassInfo
$classInfo, array $givenConstructorArguments) {
193 $className = $classInfo->getClassName();
194 $classIsSingleton = $classInfo->getIsSingleton();
195 if ($classIsSingleton && count($givenConstructorArguments) > 0) {
196 throw new \TYPO3\CMS\Extbase\
Object\
Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
198 $constructorArguments = $this->getConstructorArguments($className, $classInfo, $givenConstructorArguments);
199 array_unshift($constructorArguments, $className);
200 $instance = call_user_func_array(array('TYPO3\\CMS\\Core\\Utility\\GeneralUtility', 'makeInstance'), $constructorArguments);
201 if ($classIsSingleton) {
202 $this->singletonInstances
[$className] = $instance;
208 * Inject setter-dependencies into $instance
210 * @param object $instance
211 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
214 protected function injectDependencies($instance, \TYPO3\CMS\Extbase\
Object\Container\ClassInfo
$classInfo) {
215 if (!$classInfo->hasInjectMethods() && !$classInfo->hasInjectProperties()) {
218 foreach ($classInfo->getInjectMethods() as $injectMethodName => $classNameToInject) {
219 $instanceToInject = $this->getInstanceInternal($classNameToInject);
220 if ($classInfo->getIsSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface
) {
221 $this->log('The singleton "' . $classInfo->getClassName() . '" needs a prototype in "' . $injectMethodName . '". This is often a bad code smell; often you rather want to inject a singleton.', 1);
223 if (is_callable(array($instance, $injectMethodName))) {
224 $instance->{$injectMethodName}($instanceToInject);
227 foreach ($classInfo->getInjectProperties() as $injectPropertyName => $classNameToInject) {
228 $instanceToInject = $this->getInstanceInternal($classNameToInject);
229 if ($classInfo->getIsSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface
) {
230 $this->log('The singleton "' . $classInfo->getClassName() . '" needs a prototype in "' . $injectPropertyName . '". This is often a bad code smell; often you rather want to inject a singleton.', 1320177676);
232 $propertyReflection = \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance('TYPO3\\CMS\\Extbase\\Reflection\\PropertyReflection', $instance, $injectPropertyName);
234 $propertyReflection->setAccessible(TRUE);
235 $propertyReflection->setValue($instance, $instanceToInject);
240 * Wrapper for dev log, in order to ease testing
242 * @param string $message Message (in english).
243 * @param integer $severity Severity: 0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
246 protected function log($message, $severity) {
247 \TYPO3\CMS\Core\Utility\GeneralUtility
::devLog($message, 'extbase', $severity);
251 * register a classname that should be used if a dependency is required.
252 * e.g. used to define default class for a interface
254 * @param string $className
255 * @param string $alternativeClassName
257 public function registerImplementation($className, $alternativeClassName) {
258 $this->alternativeImplementation
[$className] = $alternativeClassName;
262 * gets array of parameter that can be used to call a constructor
264 * @param string $className
265 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
266 * @param array $givenConstructorArguments
267 * @throws \InvalidArgumentException
270 private function getConstructorArguments($className, \TYPO3\CMS\Extbase\
Object\Container\ClassInfo
$classInfo, array $givenConstructorArguments) {
271 $parameters = array();
272 $constructorArgumentInformation = $classInfo->getConstructorArguments();
273 foreach ($constructorArgumentInformation as $argumentInformation) {
274 // We have a dependency we can automatically wire,
275 // AND the class has NOT been explicitely passed in
276 if (isset($argumentInformation['dependency']) && !(count($givenConstructorArguments) && is_a($givenConstructorArguments[0], $argumentInformation['dependency']))) {
278 $parameter = $this->getInstanceInternal($argumentInformation['dependency']);
279 if ($classInfo->getIsSingleton() && !$parameter instanceof \TYPO3\CMS\Core\SingletonInterface
) {
280 $this->log('The singleton "' . $className . '" needs a prototype in the constructor. This is often a bad code smell; often you rather want to inject a singleton.', 1);
282 } elseif (count($givenConstructorArguments)) {
284 // No dependency injectable anymore, but we still have
285 // an explicit constructor argument
287 // the passed constructor argument matches the type for the dependency
288 // injection, and thus the passed constructor takes precendence over
290 $parameter = array_shift($givenConstructorArguments);
291 } elseif (array_key_exists('defaultValue', $argumentInformation)) {
292 // no value to set anymore, we take default value
293 $parameter = $argumentInformation['defaultValue'];
295 throw new \
InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
297 $parameters[] = $parameter;
303 * Returns the class name for a new instance, taking into account the
304 * class-extension API.
306 * @param string $className Base class name to evaluate
307 * @return string Final class name to instantiate with "new [classname]
309 protected function getImplementationClassName($className) {
310 if (isset($this->alternativeImplementation
[$className])) {
311 $className = $this->alternativeImplementation
[$className];
313 if (substr($className, -9) === 'Interface') {
314 $className = substr($className, 0, -9);
320 * Gets Classinfos for the className - using the cache and the factory
322 * @param string $className
323 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfo
325 private function getClassInfo($className) {
326 $classNameHash = md5($className);
327 $classInfo = $this->getClassInfoCache()->get($classNameHash);
328 if (!$classInfo instanceof \TYPO3\CMS\Extbase\
Object\Container\ClassInfo
) {
329 $classInfo = $this->getClassInfoFactory()->buildClassInfoFromClassName($className);
330 $this->getClassInfoCache()->set($classNameHash, $classInfo);
336 * @param string $className
340 public function isSingleton($className) {
341 return $this->getClassInfo($className)->getIsSingleton();
345 * @param string $className
349 public function isPrototype($className) {
350 return !$this->isSingleton($className);