2 /***************************************************************
5 * (c) 2010 Extbase Team
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
29 * TYPO3 Dependency Injection container
31 * $container = Tx_Extbase_Object_Container_Container::getContainer()
33 * @author Daniel Pötzinger
35 class Tx_Extbase_Object_Container_Container
{
38 * PHP singleton impelementation
40 * @var Tx_Extbase_Object_Container_Container
42 static private $containerInstance = null;
45 * internal cache for classinfos
47 * @var Tx_Extbase_Object_Container_ClassInfoCache
52 * registered alternative implementations of a class
53 * e.g. used to know the class for a AbstractClass or a Dependency
57 private $alternativeImplementation;
60 * reference to the classinfofactory, that analyses dependencys
61 * @var classInfoFactory
63 private $classInfoFactory;
66 * holds references of singletons
69 private $singletonInstances = array();
72 * holds references of objects that still needs setter injection processing
75 private $setterInjectionRegistry = array();
78 * Constructor is protected since container should
85 protected function __construct() {
86 $this->classInfoFactory
= new Tx_Extbase_Object_Container_ClassInfoFactory();
87 $this->cache
= new Tx_Extbase_Object_Container_ClassInfoCache();
91 * Returns an instance of the container singleton.
93 * @return Tx_Extbase_Object_Container_Container
95 static public function getContainer() {
96 if (self
::$containerInstance === NULL) {
97 self
::$containerInstance = new Tx_Extbase_Object_Container_Container();
99 return self
::$containerInstance;
102 private function __clone() {}
105 * gets an instance of the given class
106 * @param string $className
109 public function getInstance($className) {
110 $givenConstructorArguments=array();
111 if (func_num_args() > 1) {
112 $givenConstructorArguments = func_get_args();
113 array_shift($givenConstructorArguments);
115 $object = $this->getInstanceFromClassName($className, $givenConstructorArguments, 0);
116 $this->processSetterInjectionRegistry();
121 * register a classname that should be used if a dependency is required.
122 * e.g. used to define default class for a interface
124 * @param string $className
125 * @param string $alternativeClassName
127 public function registerImplementation($className,$alternativeClassName) {
128 $this->alternativeImplementation
[$className] = $alternativeClassName;
132 * gets an instance of the given class
133 * @param string $className
134 * @param array $givenConstructorArguments
136 private function getInstanceFromClassName($className, array $givenConstructorArguments=array(), $level=0) {
138 throw new Tx_Extbase_Object_Container_Exception_TooManyRecursionLevelsException('Too many recursion levels. This should never happen, please file a bug!' . $className, 1289386945);
140 if ($className === 'Tx_Extbase_Object_Container_Container') {
143 if (isset($this->singletonInstances
[$className])) {
144 return $this->singletonInstances
[$className];
147 $className = $this->getClassName($className);
149 $classInfo = $this->getClassInfo($className);
151 $constructorArguments = $this->getConstructorArguments($classInfo->getConstructorArguments(), $givenConstructorArguments, $level);
152 $instance = $this->newObject($className, $constructorArguments);
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);
158 if ($classInfo->hasInjectMethods()) {
159 $this->setterInjectionRegistry
[]=array($instance, $classInfo->getInjectMethods(), $level);
162 if ($instance instanceof t3lib_Singleton
) {
163 $this->singletonInstances
[$className] = $instance;
169 * returns a object of the given type, called with the constructor arguments.
170 * For speed improvements reflection is avoided
172 * @param string $className
173 * @param array $constructorArguments
175 private function newObject($className, array $constructorArguments) {
176 array_unshift($constructorArguments, $className);
177 return call_user_func_array(array('t3lib_div', 'makeInstance'), $constructorArguments);
181 * gets array of parameter that can be used to call a constructor
183 * @param array $constructorArgumentInformation
184 * @param array $givenConstructorArguments
187 private function getConstructorArguments(array $constructorArgumentInformation, array $givenConstructorArguments, $level) {
189 foreach ($constructorArgumentInformation as $argumentInformation) {
190 $argumentName = $argumentInformation['name'];
192 if (count($givenConstructorArguments)) {
193 // we have a value to set
194 $parameter = array_shift($givenConstructorArguments);
195 } elseif (isset($argumentInformation['dependency'])) {
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'];
202 throw new InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
204 $parameters[] = $parameter;
210 * Returns the class name for a new instance, taking into account the
211 * class-extension API.
213 * @param string Base class name to evaluate
214 * @return string Final class name to instantiate with "new [classname]"
216 protected function getClassName($className) {
217 if (isset($this->alternativeImplementation
[$className])) {
218 $className = $this->alternativeImplementation
[$className];
221 if (substr($className, -9) === 'Interface') {
222 $className = substr($className, 0, -9);
229 * do inject dependecies to object $instance using the given methods
231 * @param object $instance
232 * @param array $setterMethods
233 * @param integer $level
235 private function handleSetterInjection($instance, array $setterMethods, $level) {
236 foreach ($setterMethods as $method => $dependency) {
237 $instance->$method($this->getInstanceFromClassName($dependency, array(), $level+
1));
242 * Gets Classinfos for the className - using the cache and the factory
244 * @param string $className
245 * @return Tx_Extbase_Object_Container_ClassInfo
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));
253 return $this->cache
->get($className);
257 * does setter injection based on the data in $this->setterInjectionRegistry
258 * Its done as long till no setters are left
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]);