17553a0c318f7b2c615dd4a8b36f1200af5259fe
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Object / Container / Container.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Object\Container;
3
4 /***************************************************************
5 * Copyright notice
6 *
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.
9 * All rights reserved
10 *
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.
16 *
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.
21 *
22 *
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.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * Internal TYPO3 Dependency Injection container
32 *
33 * @author Daniel Pötzinger
34 * @author Sebastian Kurfürst
35 * @author Timo Schmidt
36 */
37 class Container implements \TYPO3\CMS\Core\SingletonInterface {
38
39 /**
40 * internal cache for classinfos
41 *
42 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
43 */
44 private $cache = NULL;
45
46 /**
47 * registered alternative implementations of a class
48 * e.g. used to know the class for a AbstractClass or a Dependency
49 *
50 * @var array
51 */
52 private $alternativeImplementation;
53
54 /**
55 * reference to the classinfofactory, that analyses dependencys
56 *
57 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
58 */
59 private $classInfoFactory = NULL;
60
61 /**
62 * holds references of singletons
63 *
64 * @var array
65 */
66 private $singletonInstances = array();
67
68 /**
69 * Array of prototype objects currently being built, to prevent recursion.
70 *
71 * @var array
72 */
73 private $prototypeObjectsWhichAreCurrentlyInstanciated;
74
75 /**
76 * Constructor is protected since container should
77 * be a singleton.
78 *
79 * @see getContainer()
80 */
81 public function __construct() {
82 }
83
84 /**
85 * Internal method to create the classInfoFactory, extracted to be mockable.
86 *
87 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
88 */
89 protected function getClassInfoFactory() {
90 if ($this->classInfoFactory == NULL) {
91 $this->classInfoFactory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoFactory');
92 }
93 return $this->classInfoFactory;
94 }
95
96 /**
97 * Internal method to create the classInfoCache, extracted to be mockable.
98 *
99 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
100 */
101 protected function getClassInfoCache() {
102 if ($this->cache == NULL) {
103 $this->cache = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoCache');
104 }
105 return $this->cache;
106 }
107
108 /**
109 * Main method which should be used to get an instance of the wished class
110 * specified by $className.
111 *
112 * @param string $className
113 * @param array $givenConstructorArguments the list of constructor arguments as array
114 * @return object the built object
115 */
116 public function getInstance($className, $givenConstructorArguments = array()) {
117 $this->prototypeObjectsWhichAreCurrentlyInstanciated = array();
118 return $this->getInstanceInternal($className, $givenConstructorArguments);
119 }
120
121 /**
122 * Create an instance of $className without calling its constructor
123 *
124 * @param string $className
125 * @return object
126 */
127 public function getEmptyObject($className) {
128 $className = $this->getImplementationClassName($className);
129 $classInfo = $this->getClassInfo($className);
130 // get an object and avoid calling __construct()
131 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
132 $this->injectDependencies($object, $classInfo);
133 return $object;
134 }
135
136 /**
137 * Internal implementation for getting a class.
138 *
139 * @param string $className
140 * @param array $givenConstructorArguments the list of constructor arguments as array
141 * @throws \TYPO3\CMS\Extbase\Object\Exception
142 * @throws \TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException
143 * @return object the built object
144 */
145 protected function getInstanceInternal($className, $givenConstructorArguments = array()) {
146 $className = $this->getImplementationClassName($className);
147 if ($className === 'TYPO3\\CMS\\Extbase\\Object\\Container\\Container') {
148 return $this;
149 }
150 if ($className === 'TYPO3\\CMS\\Core\\Cache\\CacheManager') {
151 return $GLOBALS['typo3CacheManager'];
152 }
153 $className = \TYPO3\CMS\Core\Core\ClassLoader::getClassNameForAlias($className);
154 if (isset($this->singletonInstances[$className])) {
155 if (count($givenConstructorArguments) > 0) {
156 throw new \TYPO3\CMS\Extbase\Object\Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
157 }
158 return $this->singletonInstances[$className];
159 }
160 $classInfo = $this->getClassInfo($className);
161 $classIsSingleton = $classInfo->getIsSingleton();
162 if (!$classIsSingleton) {
163 if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated) !== FALSE) {
164 throw new \TYPO3\CMS\Extbase\Object\Exception\CannotBuildObjectException('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
165 }
166 $this->prototypeObjectsWhichAreCurrentlyInstanciated[$className] = TRUE;
167 }
168 $instance = $this->instanciateObject($classInfo, $givenConstructorArguments);
169 $this->injectDependencies($instance, $classInfo);
170 if ($classInfo->getIsInitializeable() && is_callable(array($instance, 'initializeObject'))) {
171 $instance->initializeObject();
172 }
173 if (!$classIsSingleton) {
174 unset($this->prototypeObjectsWhichAreCurrentlyInstanciated[$className]);
175 }
176 return $instance;
177 }
178
179 /**
180 * Instanciates an object, possibly setting the constructor dependencies.
181 * Additionally, directly registers all singletons in the singleton registry,
182 * such that circular references of singletons are correctly instanciated.
183 *
184 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
185 * @param array $givenConstructorArguments
186 * @throws \TYPO3\CMS\Extbase\Object\Exception
187 * @return object the new instance
188 */
189 protected function instanciateObject(\TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo, array $givenConstructorArguments) {
190 $className = $classInfo->getClassName();
191 $classIsSingleton = $classInfo->getIsSingleton();
192 if ($classIsSingleton && count($givenConstructorArguments) > 0) {
193 throw new \TYPO3\CMS\Extbase\Object\Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
194 }
195 $constructorArguments = $this->getConstructorArguments($className, $classInfo, $givenConstructorArguments);
196 array_unshift($constructorArguments, $className);
197 $instance = call_user_func_array(array('TYPO3\\CMS\\Core\\Utility\\GeneralUtility', 'makeInstance'), $constructorArguments);
198 if ($classIsSingleton) {
199 $this->singletonInstances[$className] = $instance;
200 }
201 return $instance;
202 }
203
204 /**
205 * Inject setter-dependencies into $instance
206 *
207 * @param object $instance
208 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
209 * @return void
210 */
211 protected function injectDependencies($instance, \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo) {
212 if (!$classInfo->hasInjectMethods() && !$classInfo->hasInjectProperties()) {
213 return;
214 }
215 foreach ($classInfo->getInjectMethods() as $injectMethodName => $classNameToInject) {
216 $instanceToInject = $this->getInstanceInternal($classNameToInject);
217 if ($classInfo->getIsSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface) {
218 $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);
219 }
220 if (is_callable(array($instance, $injectMethodName))) {
221 $instance->{$injectMethodName}($instanceToInject);
222 }
223 }
224 foreach ($classInfo->getInjectProperties() as $injectPropertyName => $classNameToInject) {
225 $instanceToInject = $this->getInstanceInternal($classNameToInject);
226 if ($classInfo->getIsSingleton() && !$instanceToInject instanceof \TYPO3\CMS\Core\SingletonInterface) {
227 $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);
228 }
229 $propertyReflection = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Reflection\\PropertyReflection', $instance, $injectPropertyName);
230 $propertyReflection->setAccessible(TRUE);
231 $propertyReflection->setValue($instance, $instanceToInject);
232 }
233 }
234
235 /**
236 * Wrapper for dev log, in order to ease testing
237 *
238 * @param string $message Message (in english).
239 * @param integer $severity Severity: 0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
240 * @return void
241 */
242 protected function log($message, $severity) {
243 \TYPO3\CMS\Core\Utility\GeneralUtility::devLog($message, 'extbase', $severity);
244 }
245
246 /**
247 * register a classname that should be used if a dependency is required.
248 * e.g. used to define default class for a interface
249 *
250 * @param string $className
251 * @param string $alternativeClassName
252 */
253 public function registerImplementation($className, $alternativeClassName) {
254 $this->alternativeImplementation[$className] = $alternativeClassName;
255 }
256
257 /**
258 * gets array of parameter that can be used to call a constructor
259 *
260 * @param string $className
261 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
262 * @param array $givenConstructorArguments
263 * @throws \InvalidArgumentException
264 * @return array
265 */
266 private function getConstructorArguments($className, \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo, array $givenConstructorArguments) {
267 $parameters = array();
268 $constructorArgumentInformation = $classInfo->getConstructorArguments();
269 foreach ($constructorArgumentInformation as $argumentInformation) {
270 // We have a dependency we can automatically wire,
271 // AND the class has NOT been explicitely passed in
272 if (isset($argumentInformation['dependency']) && !(count($givenConstructorArguments) && is_a($givenConstructorArguments[0], $argumentInformation['dependency']))) {
273 // Inject parameter
274 $parameter = $this->getInstanceInternal($argumentInformation['dependency']);
275 if ($classInfo->getIsSingleton() && !$parameter instanceof \TYPO3\CMS\Core\SingletonInterface) {
276 $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);
277 }
278 } elseif (count($givenConstructorArguments)) {
279 // EITHER:
280 // No dependency injectable anymore, but we still have
281 // an explicit constructor argument
282 // OR:
283 // the passed constructor argument matches the type for the dependency
284 // injection, and thus the passed constructor takes precendence over
285 // autowiring.
286 $parameter = array_shift($givenConstructorArguments);
287 } elseif (array_key_exists('defaultValue', $argumentInformation)) {
288 // no value to set anymore, we take default value
289 $parameter = $argumentInformation['defaultValue'];
290 } else {
291 throw new \InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
292 }
293 $parameters[] = $parameter;
294 }
295 return $parameters;
296 }
297
298 /**
299 * Returns the class name for a new instance, taking into account the
300 * class-extension API.
301 *
302 * @param string $className Base class name to evaluate
303 * @return string Final class name to instantiate with "new [classname]
304 */
305 protected function getImplementationClassName($className) {
306 if (isset($this->alternativeImplementation[$className])) {
307 $className = $this->alternativeImplementation[$className];
308 }
309 if (substr($className, -9) === 'Interface') {
310 $className = substr($className, 0, -9);
311 }
312 return $className;
313 }
314
315 /**
316 * Gets Classinfos for the className - using the cache and the factory
317 *
318 * @param string $className
319 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfo
320 */
321 private function getClassInfo($className) {
322 $classNameHash = md5($className);
323 $classInfo = $this->getClassInfoCache()->get($classNameHash);
324 if (!$classInfo instanceof \TYPO3\CMS\Extbase\Object\Container\ClassInfo) {
325 $classInfo = $this->getClassInfoFactory()->buildClassInfoFromClassName($className);
326 $this->getClassInfoCache()->set($classNameHash, $classInfo);
327 }
328 return $classInfo;
329 }
330 }
331
332 ?>