TYPO3 API  SVNRelease
Container.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2010 Extbase Team
00006 *  All rights reserved
00007 *
00008 *  This class is a backport of the corresponding class of FLOW3.
00009 *  All credits go to the v5 team.
00010 *
00011 *  This script is part of the TYPO3 project. The TYPO3 project is
00012 *  free software; you can redistribute it and/or modify
00013 *  it under the terms of the GNU General Public License as published by
00014 *  the Free Software Foundation; either version 2 of the License, or
00015 *  (at your option) any later version.
00016 *
00017 *  The GNU General Public License can be found at
00018 *  http://www.gnu.org/copyleft/gpl.html.
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 /**
00029  * Internal TYPO3 Dependency Injection container
00030  *
00031  * @author Daniel Pötzinger
00032  * @author Sebastian Kurfürst
00033  */
00034 class Tx_Extbase_Object_Container_Container implements t3lib_Singleton {
00035 
00036     /**
00037      * internal cache for classinfos
00038      *
00039      * @var Tx_Extbase_Object_Container_ClassInfoCache
00040      */
00041     private $cache;
00042 
00043     /**
00044      * registered alternative implementations of a class
00045      * e.g. used to know the class for a AbstractClass or a Dependency
00046      *
00047      * @var array
00048      */
00049     private $alternativeImplementation;
00050 
00051     /**
00052      * reference to the classinfofactory, that analyses dependencys
00053      *
00054      * @var Tx_Extbase_Object_Container_ClassInfoFactory
00055      */
00056     private $classInfoFactory;
00057 
00058     /**
00059      * holds references of singletons
00060      *
00061      * @var array
00062      */
00063     private $singletonInstances = array();
00064 
00065     /**
00066      * Array of prototype objects currently being built, to prevent recursion.
00067      *
00068      * @var array
00069      */
00070     private $prototypeObjectsWhichAreCurrentlyInstanciated;
00071 
00072     /**
00073      * Constructor is protected since container should
00074      * be a singleton.
00075      *
00076      * @see getContainer()
00077      * @param void
00078      * @return void
00079      */
00080     public function __construct() {
00081         $this->classInfoFactory = t3lib_div::makeInstance('Tx_Extbase_Object_Container_ClassInfoFactory');
00082         $this->cache = t3lib_div::makeInstance('Tx_Extbase_Object_Container_ClassInfoCache');
00083     }
00084 
00085     /**
00086      * Main method which should be used to get an instance of the wished class
00087      * specified by $className.
00088      *
00089      * @param string $className
00090      * @param array $givenConstructorArguments the list of constructor arguments as array
00091      * @return object the built object
00092      */
00093     public function getInstance($className, $givenConstructorArguments = array()) {
00094         $this->prototypeObjectsWhichAreCurrentlyInstanciated = array();
00095         return $this->getInstanceInternal($className, $givenConstructorArguments);
00096     }
00097 
00098     /**
00099      * Internal implementation for getting a class.
00100      *
00101      * @param string $className
00102      * @param array $givenConstructorArguments the list of constructor arguments as array
00103      * @return object the built object
00104      */
00105     protected function getInstanceInternal($className, $givenConstructorArguments = array()) {
00106         $className = $this->getImplementationClassName($className);
00107 
00108         if ($className === 'Tx_Extbase_Object_Container_Container') {
00109             return $this;
00110         }
00111 
00112         if (isset($this->singletonInstances[$className])) {
00113             if (count($givenConstructorArguments) > 0) {
00114                 throw new Tx_Extbase_Object_Exception('Object "' . $className . '" fetched from singleton cache, thus, explicit constructor arguments are not allowed.', 1292857934);
00115             }
00116             return $this->singletonInstances[$className];
00117         }
00118 
00119         $classIsSingleton = $this->isSingleton($className);
00120         if (!$classIsSingleton) {
00121             if (array_search($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated) !== FALSE) {
00122                 throw new Tx_Extbase_Object_Exception_CannotBuildObject('Cyclic dependency in prototype object, for class "' . $className . '".', 1295611406);
00123             }
00124             $this->prototypeObjectsWhichAreCurrentlyInstanciated[] = $className;
00125         }
00126 
00127         $classInfo = $this->getClassInfo($className);
00128 
00129         $instance = $this->instanciateObject($className, $classInfo, $givenConstructorArguments);
00130         $this->injectDependencies($instance, $classInfo);
00131 
00132         if (method_exists($instance, 'initializeObject') && is_callable(array($instance, 'initializeObject'))) {
00133             $instance->initializeObject();
00134         }
00135 
00136         if (!$classIsSingleton) {
00137             array_pop($this->prototypeObjectsWhichAreCurrentlyInstanciated);
00138         }
00139 
00140         return $instance;
00141     }
00142 
00143     /**
00144      * Instanciates an object, possibly setting the constructor dependencies.
00145      * Additionally, directly registers all singletons in the singleton registry,
00146      * such that circular references of singletons are correctly instanciated.
00147      *
00148      * @param string $className
00149      * @param Tx_Extbase_Object_Container_ClassInfo $classInfo
00150      * @param array $givenConstructorArguments
00151      * @return object the new instance
00152      */
00153     protected function instanciateObject($className, Tx_Extbase_Object_Container_ClassInfo $classInfo, array $givenConstructorArguments) {
00154         $classIsSingleton = $this->isSingleton($className);
00155 
00156         if ($classIsSingleton && count($givenConstructorArguments) > 0) {
00157             throw new Tx_Extbase_Object_Exception('Object "' . $className . '" has explicit constructor arguments but is a singleton; this is not allowed.', 1292858051);
00158         }
00159 
00160         $constructorArguments = $this->getConstructorArguments($className, $classInfo->getConstructorArguments(), $givenConstructorArguments);
00161         array_unshift($constructorArguments, $className);
00162         $instance = call_user_func_array(array('t3lib_div', 'makeInstance'), $constructorArguments);
00163 
00164         if ($classIsSingleton) {
00165             $this->singletonInstances[$className] = $instance;
00166         }
00167         return $instance;
00168     }
00169 
00170     /**
00171      * Inject setter-dependencies into $instance
00172      *
00173      * @param object $instance
00174      * @param Tx_Extbase_Object_Container_ClassInfo $classInfo
00175      * @return void
00176      */
00177     protected function injectDependencies($instance, Tx_Extbase_Object_Container_ClassInfo $classInfo) {
00178         if (!$classInfo->hasInjectMethods()) return;
00179 
00180         foreach ($classInfo->getInjectMethods() as $injectMethodName => $classNameToInject) {
00181 
00182             $instanceToInject = $this->getInstanceInternal($classNameToInject);
00183             if ($this->isSingleton($instance) && !($instanceToInject instanceof t3lib_Singleton)) {
00184                 $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);
00185             }
00186 
00187             $instance->$injectMethodName($instanceToInject);
00188         }
00189     }
00190 
00191     /**
00192      * Wrapper for dev log, in order to ease testing
00193      *
00194      * @param   string      Message (in english).
00195      * @param   integer     Severity: 0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
00196      * @return  void
00197      */
00198     protected function log($message, $severity) {
00199         t3lib_div::devLog($message, 'extbase', $severity);
00200     }
00201 
00202     /**
00203      * register a classname that should be used if a dependency is required.
00204      * e.g. used to define default class for a interface
00205      *
00206      * @param string $className
00207      * @param string $alternativeClassName
00208      */
00209     public function registerImplementation($className,$alternativeClassName) {
00210         $this->alternativeImplementation[$className] = $alternativeClassName;
00211     }
00212 
00213     /**
00214      * gets array of parameter that can be used to call a constructor
00215      *
00216      * @param string $className
00217      * @param array $constructorArgumentInformation
00218      * @param array $givenConstructorArguments
00219      * @return array
00220      */
00221     private function getConstructorArguments($className, array $constructorArgumentInformation, array $givenConstructorArguments) {
00222         $parameters=array();
00223         foreach ($constructorArgumentInformation as $argumentInformation) {
00224             $argumentName = $argumentInformation['name'];
00225 
00226             // We have a dependency we can automatically wire,
00227             // AND the class has NOT been explicitely passed in
00228             if (isset($argumentInformation['dependency']) && !(count($givenConstructorArguments) && is_a($givenConstructorArguments[0], $argumentInformation['dependency']))) {
00229                 // Inject parameter
00230                 $parameter = $this->getInstanceInternal($argumentInformation['dependency']);
00231                 if ($this->isSingleton($className) && !($parameter instanceof t3lib_Singleton)) {
00232                     $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);
00233                 }
00234             } elseif (count($givenConstructorArguments)) {
00235                 // EITHER:
00236                 // No dependency injectable anymore, but we still have
00237                 // an explicit constructor argument
00238                 // OR:
00239                 // the passed constructor argument matches the type for the dependency
00240                 // injection, and thus the passed constructor takes precendence over
00241                 // autowiring.
00242                 $parameter = array_shift($givenConstructorArguments);
00243             } elseif (array_key_exists('defaultValue', $argumentInformation)) {
00244                 // no value to set anymore, we take default value
00245                 $parameter = $argumentInformation['defaultValue'];
00246             } else {
00247                 throw new InvalidArgumentException('not a correct info array of constructor dependencies was passed!');
00248             }
00249             $parameters[] = $parameter;
00250         }
00251         return $parameters;
00252     }
00253 
00254     /**
00255      * @param string/object $object
00256      * @return boolean TRUE if the object is a singleton, FALSE if it is a prototype.
00257      */
00258     protected function isSingleton($object) {
00259         return in_array('t3lib_Singleton', class_implements($object));
00260     }
00261     /**
00262      * Returns the class name for a new instance, taking into account the
00263      * class-extension API.
00264      *
00265      * @param   string      Base class name to evaluate
00266      * @return  string      Final class name to instantiate with "new [classname]"
00267      */
00268     protected function getImplementationClassName($className) {
00269         if (isset($this->alternativeImplementation[$className])) {
00270             $className = $this->alternativeImplementation[$className];
00271         }
00272 
00273         if (substr($className, -9) === 'Interface') {
00274             $className = substr($className, 0, -9);
00275         }
00276 
00277         return $className;
00278     }
00279 
00280     /**
00281      * Gets Classinfos for the className - using the cache and the factory
00282      *
00283      * @param string $className
00284      * @return Tx_Extbase_Object_Container_ClassInfo
00285      */
00286     private function getClassInfo($className) {
00287             // we also need to make sure that the cache is returning a vaild object
00288             // in case something went wrong with unserialization etc..
00289         if (!$this->cache->has($className) || !is_object($this->cache->get($className))) {
00290             $this->cache->set($className, $this->classInfoFactory->buildClassInfoFromClassName($className));
00291         }
00292         return $this->cache->get($className);
00293     }
00294 }