[!!!][+TASK] Extbase (Object): Rewritten Object Container
[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 * Internal TYPO3 Dependency Injection container
30 *
31 * @author Daniel Pötzinger
32 * @author Sebastian Kurfürst
33 */
34 class Tx_Extbase_Object_Container_Container implements t3lib_Singleton {
35
36 /**
37 * internal cache for classinfos
38 *
39 * @var Tx_Extbase_Object_Container_ClassInfoCache
40 */
41 private $cache;
42
43 /**
44 * registered alternative implementations of a class
45 * e.g. used to know the class for a AbstractClass or a Dependency
46 *
47 * @var array
48 */
49 private $alternativeImplementation;
50
51 /**
52 * reference to the classinfofactory, that analyses dependencys
53 *
54 * @var Tx_Extbase_Object_Container_ClassInfoFactory
55 */
56 private $classInfoFactory;
57
58 /**
59 * holds references of singletons
60 *
61 * @var array
62 */
63 private $singletonInstances = array();
64
65 /**
66 * Constructor is protected since container should
67 * be a singleton.
68 *
69 * @see getContainer()
70 * @param void
71 * @return void
72 */
73 public function __construct() {
74 $this->classInfoFactory = t3lib_div::makeInstance('Tx_Extbase_Object_Container_ClassInfoFactory');
75 $this->cache = t3lib_div::makeInstance('Tx_Extbase_Object_Container_ClassInfoCache');
76 }
77
78 /**
79 * gets an instance of the given class
80 * @param string $className
81 * @return object
82 */
83 public function getInstance($className, $givenConstructorArguments = array()) {
84 $className = $this->getImplementationClassName($className);
85
86 if ($className === 'Tx_Extbase_Object_Container_Container') {
87 return $this;
88 }
89
90 if (isset($this->singletonInstances[$className])) {
91 if (count($givenConstructorArguments) > 0) {
92 throw new Tx_Extbase_Object_Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
93 }
94 return $this->singletonInstances[$className];
95 }
96 $classInfo = $this->getClassInfo($className);
97
98 $instance = $this->instanciateObject($className, $classInfo, $givenConstructorArguments);
99 $this->injectDependencies($instance, $classInfo);
100
101 if (method_exists($instance, 'initializeObject') && is_callable(array($instance, 'initializeObject'))) {
102 $instance->initializeObject();
103 }
104
105 return $instance;
106 }
107
108 /**
109 * Instanciates an object, possibly setting the constructor dependencies.
110 * Additionally, directly registers all singletons in the singleton registry,
111 * such that circular references of singletons are correctly instanciated.
112 *
113 * @param <type> $className
114 * @param Tx_Extbase_Object_Container_ClassInfo $classInfo
115 * @param array $givenConstructorArguments
116 * @return <type>
117 */
118 protected function instanciateObject($className, Tx_Extbase_Object_Container_ClassInfo $classInfo, array $givenConstructorArguments) {
119 $classIsSingleton = $this->isSingleton($className);
120
121 if ($classIsSingleton && count($givenConstructorArguments) > 0) {
122 throw new Tx_Extbase_Object_Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
123 }
124
125 $constructorArguments = $this->getConstructorArguments($classInfo->getConstructorArguments(), $givenConstructorArguments, $level);
126 array_unshift($constructorArguments, $className);
127 $instance = call_user_func_array(array('t3lib_div', 'makeInstance'), $constructorArguments);
128
129 if ($classIsSingleton) {
130 $this->singletonInstances[$className] = $instance;
131 }
132 return $instance;
133 }
134
135 protected function injectDependencies($instance, Tx_Extbase_Object_Container_ClassInfo $classInfo) {
136 if (!$classInfo->hasInjectMethods()) return;
137
138 foreach ($classInfo->getInjectMethods() as $injectMethodName => $classNameToInject) {
139
140 $instanceToInject = $this->getInstance($classNameToInject);
141 if (!$instanceToInject instanceof t3lib_Singleton) {
142 throw new Tx_Extbase_Object_Exception_WrongScope('Setter dependencies can only be singletons', 1292860859);
143 }
144 $instance->$injectMethodName($instanceToInject);
145 }
146 }
147
148 /**
149 * register a classname that should be used if a dependency is required.
150 * e.g. used to define default class for a interface
151 *
152 * @param string $className
153 * @param string $alternativeClassName
154 */
155 public function registerImplementation($className,$alternativeClassName) {
156 $this->alternativeImplementation[$className] = $alternativeClassName;
157 }
158
159 /**
160 * gets array of parameter that can be used to call a constructor
161 *
162 * @param array $constructorArgumentInformation
163 * @param array $givenConstructorArguments
164 * @return array
165 */
166 private function getConstructorArguments(array $constructorArgumentInformation, array $givenConstructorArguments, $level) {
167 $parameters=array();
168 foreach ($constructorArgumentInformation as $argumentInformation) {
169 $argumentName = $argumentInformation['name'];
170
171 // We have a dependency we can automatically wire,
172 // AND the class has NOT been explicitely passed in
173 if (isset($argumentInformation['dependency']) && !(count($givenConstructorArguments) && is_a($givenConstructorArguments[0], $argumentInformation['dependency']))) {
174 // Inject parameter
175 if (!$this->isSingleton($argumentInformation['dependency'])) {
176 throw new Tx_Extbase_Object_Exception_WrongScope('Constructor dependencies can only be singletons', 1292860858);
177 }
178 $parameter = $this->getInstance($argumentInformation['dependency']);
179 } elseif (count($givenConstructorArguments)) {
180 // EITHER:
181 // No dependency injectable anymore, but we still have
182 // an explicit constructor argument
183 // OR:
184 // the passed constructor argument matches the type for the dependency
185 // injection, and thus the passed constructor takes precendence over
186 // autowiring.
187 $parameter = array_shift($givenConstructorArguments);
188 } elseif (isset($argumentInformation['defaultValue'])) {
189 // no value to set anymore, we take default value
190 $parameter = $argumentInformation['defaultValue'];
191 } else {
192 throw new InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
193 }
194 $parameters[] = $parameter;
195 }
196 return $parameters;
197 }
198
199
200 protected function isSingleton($object) {
201 return in_array('t3lib_Singleton', class_implements($object));
202 }
203 /**
204 * Returns the class name for a new instance, taking into account the
205 * class-extension API.
206 *
207 * @param string Base class name to evaluate
208 * @return string Final class name to instantiate with "new [classname]"
209 */
210 protected function getImplementationClassName($className) {
211 if (isset($this->alternativeImplementation[$className])) {
212 $className = $this->alternativeImplementation[$className];
213 }
214
215 if (substr($className, -9) === 'Interface') {
216 $className = substr($className, 0, -9);
217 }
218
219 return $className;
220 }
221
222 /**
223 * Gets Classinfos for the className - using the cache and the factory
224 *
225 * @param string $className
226 * @return Tx_Extbase_Object_Container_ClassInfo
227 */
228 private function getClassInfo($className) {
229 // we also need to make sure that the cache is returning a vaild object
230 // in case something went wrong with unserialization etc..
231 if (!$this->cache->has($className) || !is_object($this->cache->get($className))) {
232 $this->cache->set($className, $this->classInfoFactory->buildClassInfoFromClassName($className));
233 }
234 return $this->cache->get($className);
235 }
236 }