[BUGFIX] Add table as parameter to CObjectViewHelper
[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 const SCOPE_PROTOTYPE = 1;
40 const SCOPE_SINGLETON = 2;
41
42 /**
43 * internal cache for classinfos
44 *
45 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
46 */
47 private $cache = NULL;
48
49 /**
50 * registered alternative implementations of a class
51 * e.g. used to know the class for a AbstractClass or a Dependency
52 *
53 * @var array
54 */
55 private $alternativeImplementation;
56
57 /**
58 * reference to the classinfofactory, that analyses dependencys
59 *
60 * @var \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
61 */
62 private $classInfoFactory = NULL;
63
64 /**
65 * holds references of singletons
66 *
67 * @var array
68 */
69 private $singletonInstances = array();
70
71 /**
72 * Array of prototype objects currently being built, to prevent recursion.
73 *
74 * @var array
75 */
76 private $prototypeObjectsWhichAreCurrentlyInstanciated;
77
78 /**
79 * Constructor is protected since container should
80 * be a singleton.
81 *
82 * @see getContainer()
83 */
84 public function __construct() {
85 }
86
87 /**
88 * Internal method to create the classInfoFactory, extracted to be mockable.
89 *
90 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoFactory
91 */
92 protected function getClassInfoFactory() {
93 if ($this->classInfoFactory == NULL) {
94 $this->classInfoFactory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoFactory');
95 }
96 return $this->classInfoFactory;
97 }
98
99 /**
100 * Internal method to create the classInfoCache, extracted to be mockable.
101 *
102 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfoCache
103 */
104 protected function getClassInfoCache() {
105 if ($this->cache == NULL) {
106 $this->cache = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\Container\\ClassInfoCache');
107 }
108 return $this->cache;
109 }
110
111 /**
112 * Main method which should be used to get an instance of the wished class
113 * specified by $className.
114 *
115 * @param string $className
116 * @param array $givenConstructorArguments the list of constructor arguments as array
117 * @return object the built object
118 */
119 public function getInstance($className, $givenConstructorArguments = array()) {
120 $this->prototypeObjectsWhichAreCurrentlyInstanciated = array();
121 return $this->getInstanceInternal($className, $givenConstructorArguments);
122 }
123
124 /**
125 * Create an instance of $className without calling its constructor
126 *
127 * @param string $className
128 * @return object
129 */
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);
136 return $object;
137 }
138
139 /**
140 * Internal implementation for getting a class.
141 *
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
147 */
148 protected function getInstanceInternal($className, $givenConstructorArguments = array()) {
149 $className = $this->getImplementationClassName($className);
150 if ($className === 'TYPO3\\CMS\\Extbase\\Object\\Container\\Container') {
151 return $this;
152 }
153 if ($className === 'TYPO3\\CMS\\Core\\Cache\\CacheManager') {
154 return $GLOBALS['typo3CacheManager'];
155 }
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);
160 }
161 return $this->singletonInstances[$className];
162 }
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);
168 }
169 $this->prototypeObjectsWhichAreCurrentlyInstanciated[$className] = TRUE;
170 }
171 $instance = $this->instanciateObject($classInfo, $givenConstructorArguments);
172 $this->injectDependencies($instance, $classInfo);
173 if ($classInfo->getIsInitializeable() && is_callable(array($instance, 'initializeObject'))) {
174 $instance->initializeObject();
175 }
176 if (!$classIsSingleton) {
177 unset($this->prototypeObjectsWhichAreCurrentlyInstanciated[$className]);
178 }
179 return $instance;
180 }
181
182 /**
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.
186 *
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
191 */
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);
197 }
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;
203 }
204 return $instance;
205 }
206
207 /**
208 * Inject setter-dependencies into $instance
209 *
210 * @param object $instance
211 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
212 * @return void
213 */
214 protected function injectDependencies($instance, \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo) {
215 if (!$classInfo->hasInjectMethods() && !$classInfo->hasInjectProperties()) {
216 return;
217 }
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);
222 }
223 if (is_callable(array($instance, $injectMethodName))) {
224 $instance->{$injectMethodName}($instanceToInject);
225 }
226 }
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);
231 }
232 $propertyReflection = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Reflection\\PropertyReflection', $instance, $injectPropertyName);
233
234 $propertyReflection->setAccessible(TRUE);
235 $propertyReflection->setValue($instance, $instanceToInject);
236 }
237 }
238
239 /**
240 * Wrapper for dev log, in order to ease testing
241 *
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
244 * @return void
245 */
246 protected function log($message, $severity) {
247 \TYPO3\CMS\Core\Utility\GeneralUtility::devLog($message, 'extbase', $severity);
248 }
249
250 /**
251 * register a classname that should be used if a dependency is required.
252 * e.g. used to define default class for a interface
253 *
254 * @param string $className
255 * @param string $alternativeClassName
256 */
257 public function registerImplementation($className, $alternativeClassName) {
258 $this->alternativeImplementation[$className] = $alternativeClassName;
259 }
260
261 /**
262 * gets array of parameter that can be used to call a constructor
263 *
264 * @param string $className
265 * @param \TYPO3\CMS\Extbase\Object\Container\ClassInfo $classInfo
266 * @param array $givenConstructorArguments
267 * @throws \InvalidArgumentException
268 * @return array
269 */
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']))) {
277 // Inject parameter
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);
281 }
282 } elseif (count($givenConstructorArguments)) {
283 // EITHER:
284 // No dependency injectable anymore, but we still have
285 // an explicit constructor argument
286 // OR:
287 // the passed constructor argument matches the type for the dependency
288 // injection, and thus the passed constructor takes precendence over
289 // autowiring.
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'];
294 } else {
295 throw new \InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
296 }
297 $parameters[] = $parameter;
298 }
299 return $parameters;
300 }
301
302 /**
303 * Returns the class name for a new instance, taking into account the
304 * class-extension API.
305 *
306 * @param string $className Base class name to evaluate
307 * @return string Final class name to instantiate with "new [classname]
308 */
309 protected function getImplementationClassName($className) {
310 if (isset($this->alternativeImplementation[$className])) {
311 $className = $this->alternativeImplementation[$className];
312 }
313 if (substr($className, -9) === 'Interface') {
314 $className = substr($className, 0, -9);
315 }
316 return $className;
317 }
318
319 /**
320 * Gets Classinfos for the className - using the cache and the factory
321 *
322 * @param string $className
323 * @return \TYPO3\CMS\Extbase\Object\Container\ClassInfo
324 */
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);
331 }
332 return $classInfo;
333 }
334
335 /**
336 * @param string $className
337 *
338 * @return boolean
339 */
340 public function isSingleton($className) {
341 return $this->getClassInfo($className)->getIsSingleton();
342 }
343
344 /**
345 * @param string $className
346 *
347 * @return boolean
348 */
349 public function isPrototype($className) {
350 return !$this->isSingleton($className);
351 }
352 }