[+TASK] Extbase (Object): Make sure the class info cache is returning valid objects
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Object / Container / Container.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010 Extbase Team
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
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 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * TYPO3 Dependency Injection container
30 * Initial Usage:
31 * $container = Tx_Extbase_Object_Container_Container::getContainer()
32 *
33 * @author Daniel Pötzinger
34 */
35 class Tx_Extbase_Object_Container_Container {
36
37 /**
38 * PHP singleton impelementation
39 *
40 * @var Tx_Extbase_Object_Container_Container
41 */
42 static private $containerInstance = null;
43
44 /**
45 * internal cache for classinfos
46 *
47 * @var Tx_Extbase_Object_Container_ClassInfoCache
48 */
49 private $cache;
50
51 /**
52 * registered alternative implementations of a class
53 * e.g. used to know the class for a AbstractClass or a Dependency
54 *
55 * @var array
56 */
57 private $alternativeImplementation;
58
59 /**
60 * reference to the classinfofactory, that analyses dependencys
61 * @var classInfoFactory
62 */
63 private $classInfoFactory;
64
65 /**
66 * holds references of singletons
67 * @var array
68 */
69 private $singletonInstances = array();
70
71 /**
72 * holds references of objects that still needs setter injection processing
73 * @var array
74 */
75 private $setterInjectionRegistry = array();
76
77 /**
78 * Constructor is protected since container should
79 * be a singleton.
80 *
81 * @see getContainer()
82 * @param void
83 * @return void
84 */
85 protected function __construct() {
86 $this->classInfoFactory = new Tx_Extbase_Object_Container_ClassInfoFactory();
87 $this->cache = new Tx_Extbase_Object_Container_ClassInfoCache();
88 }
89
90 /**
91 * Returns an instance of the container singleton.
92 *
93 * @return Tx_Extbase_Object_Container_Container
94 */
95 static public function getContainer() {
96 if (self::$containerInstance === NULL) {
97 self::$containerInstance = new Tx_Extbase_Object_Container_Container();
98 }
99 return self::$containerInstance;
100 }
101
102 private function __clone() {}
103
104 /**
105 * gets an instance of the given class
106 * @param string $className
107 * @return object
108 */
109 public function getInstance($className) {
110 $givenConstructorArguments=array();
111 if (func_num_args() > 1) {
112 $givenConstructorArguments = func_get_args();
113 array_shift($givenConstructorArguments);
114 }
115 $object = $this->getInstanceFromClassName($className, $givenConstructorArguments, 0);
116 $this->processSetterInjectionRegistry();
117 return $object;
118 }
119
120 /**
121 * register a classname that should be used if a dependency is required.
122 * e.g. used to define default class for a interface
123 *
124 * @param string $className
125 * @param string $alternativeClassName
126 */
127 public function registerImplementation($className,$alternativeClassName) {
128 $this->alternativeImplementation[$className] = $alternativeClassName;
129 }
130
131 /**
132 * gets an instance of the given class
133 * @param string $className
134 * @param array $givenConstructorArguments
135 */
136 private function getInstanceFromClassName($className, array $givenConstructorArguments=array(), $level=0) {
137 if ($level > 30) {
138 throw new Tx_Extbase_Object_Container_Exception_TooManyRecursionLevelsException('Too many recursion levels. This should never happen, please file a bug!' . $className, 1289386945);
139 }
140 if ($className === 'Tx_Extbase_Object_Container_Container') {
141 return $this;
142 }
143 if (isset($this->singletonInstances[$className])) {
144 return $this->singletonInstances[$className];
145 }
146
147 $className = $this->getClassName($className);
148
149 $classInfo = $this->getClassInfo($className);
150
151 $constructorArguments = $this->getConstructorArguments($classInfo->getConstructorArguments(), $givenConstructorArguments, $level);
152 $instance = $this->newObject($className, $constructorArguments);
153
154 if ($level > 0 && !($instance instanceof t3lib_Singleton)) {
155 throw new Tx_Extbase_Object_Exception_WrongScope('Object "' . $className . '" is of not of scope singleton, but only singleton instances can be injected into other classes.', 1289387047);
156 }
157
158 if ($classInfo->hasInjectMethods()) {
159 $this->setterInjectionRegistry[]=array($instance, $classInfo->getInjectMethods(), $level);
160 }
161
162 if ($instance instanceof t3lib_Singleton) {
163 $this->singletonInstances[$className] = $instance;
164 }
165 return $instance;
166 }
167
168 /**
169 * returns a object of the given type, called with the constructor arguments.
170 * For speed improvements reflection is avoided
171 *
172 * @param string $className
173 * @param array $constructorArguments
174 */
175 private function newObject($className, array $constructorArguments) {
176 array_unshift($constructorArguments, $className);
177 return call_user_func_array(array('t3lib_div', 'makeInstance'), $constructorArguments);
178 }
179
180 /**
181 * gets array of parameter that can be used to call a constructor
182 *
183 * @param array $constructorArgumentInformation
184 * @param array $givenConstructorArguments
185 * @return array
186 */
187 private function getConstructorArguments(array $constructorArgumentInformation, array $givenConstructorArguments, $level) {
188 $parameters=array();
189 foreach ($constructorArgumentInformation as $argumentInformation) {
190 $argumentName = $argumentInformation['name'];
191
192 if (count($givenConstructorArguments)) {
193 // we have a value to set
194 $parameter = array_shift($givenConstructorArguments);
195 } elseif (isset($argumentInformation['dependency'])) {
196 // Inject parameter
197 $parameter = $this->getInstanceFromClassName($argumentInformation['dependency'], array(), $level+1);
198 } elseif (isset($argumentInformation['defaultValue'])) {
199 // no value to set anymore, we take default value
200 $parameter = $argumentInformation['defaultValue'];
201 } else {
202 throw new InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
203 }
204 $parameters[] = $parameter;
205 }
206 return $parameters;
207 }
208
209 /**
210 * Returns the class name for a new instance, taking into account the
211 * class-extension API.
212 *
213 * @param string Base class name to evaluate
214 * @return string Final class name to instantiate with "new [classname]"
215 */
216 protected function getClassName($className) {
217 if (isset($this->alternativeImplementation[$className])) {
218 $className = $this->alternativeImplementation[$className];
219 }
220
221 if (substr($className, -9) === 'Interface') {
222 $className = substr($className, 0, -9);
223 }
224
225 return $className;
226 }
227
228 /**
229 * do inject dependecies to object $instance using the given methods
230 *
231 * @param object $instance
232 * @param array $setterMethods
233 * @param integer $level
234 */
235 private function handleSetterInjection($instance, array $setterMethods, $level) {
236 foreach ($setterMethods as $method => $dependency) {
237 $instance->$method($this->getInstanceFromClassName($dependency, array(), $level+1));
238 }
239 }
240
241 /**
242 * Gets Classinfos for the className - using the cache and the factory
243 *
244 * @param string $className
245 * @return Tx_Extbase_Object_Container_ClassInfo
246 */
247 private function getClassInfo($className) {
248 // we also need to make sure that the cache is returning a vaild object
249 // in case something went wrong with unserialization etc..
250 if (!$this->cache->has($className) || !is_object($this->cache->get($className))) {
251 $this->cache->set($className, $this->classInfoFactory->buildClassInfoFromClassName($className));
252 }
253 return $this->cache->get($className);
254 }
255
256 /**
257 * does setter injection based on the data in $this->setterInjectionRegistry
258 * Its done as long till no setters are left
259 *
260 * @return void
261 */
262 private function processSetterInjectionRegistry() {
263 while (count($this->setterInjectionRegistry)>0) {
264 $currentSetterData = $this->setterInjectionRegistry;
265 $this->setterInjectionRegistry = array();
266 foreach ($currentSetterData as $setterInjectionData) {
267 $this->handleSetterInjection($setterInjectionData[0], $setterInjectionData[1], $setterInjectionData[2]);
268 }
269 }
270 }
271 }