Fixed bug #16267 Add functions to provide mocks for makeInstance (Thanks to Oliver...
authorSteffen Kamper <info@sk-typo3.de>
Wed, 1 Dec 2010 23:06:48 +0000 (23:06 +0000)
committerSteffen Kamper <info@sk-typo3.de>
Wed, 1 Dec 2010 23:06:48 +0000 (23:06 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@9744 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_div.php
tests/t3lib/t3lib_divTest.php

index 0452a81..0e90f1e 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -13,6 +13,7 @@
 
 2010-12-01  Steffen Kamper  <steffen@typo3.org>
 
+       * Fixed bug #16267 Add functions to provide mocks for makeInstance (Thanks to Oliver Klee)
        * Added Feature #16642: New extension manager part 2: Add ExtJs Module
        * Follow-up to #16234: ExtDirect Dataprovider for user settings
        * Added Feature #16641: Here comes FAL - beta2 version
index f74b56b..9d91807 100644 (file)
@@ -236,6 +236,20 @@ final class t3lib_div {
        const SYSLOG_SEVERITY_ERROR = 3;
        const SYSLOG_SEVERITY_FATAL = 4;
 
+       /**
+        * Singleton instances returned by makeInstance, using the class names as
+        * array keys
+        *
+        * @var array<t3lib_Singleton>
+        */
+       protected static $singletonInstances = array();
+
+       /**
+        * Instances returned by makeInstance, using the class names as array keys
+        *
+        * @var array<array><object>
+        */
+       protected static $nonSingletonInstances = array();
 
        /*************************
         *
@@ -5359,39 +5373,48 @@ final class t3lib_div {
         * Creates an instance of a class taking into account the class-extensions
         * API of TYPO3. USE THIS method instead of the PHP "new" keyword.
         * Eg. "$obj = new myclass;" should be "$obj = t3lib_div::makeInstance("myclass")" instead!
+        *
         * You can also pass arguments for a constructor:
-        *       t3lib_div::makeInstance('myClass', $arg1, $arg2,  ..., $argN)
+        *              t3lib_div::makeInstance('myClass', $arg1, $arg2,  ..., $argN)
         *
-        * @param       string          Class name to instantiate
-        * @return      object          A reference to the object
+        * @throws      InvalidArgumentException if classname is an empty string
+        * @param       string          $className
+        *                      name of the class to instantiate, must not be empty
+        * @return      object          the created instance
         */
        public static function makeInstance($className) {
-                       // holds references of singletons
-               static $instances = array();
+               if ($className === '') {
+                       throw new InvalidArgumentException('$classname must not be empty.', 1288965219);
+               }
+
+               $finalClassName = self::getClassName($className);
+
+                       // Return singleton instance if it is already registered
+               if (isset(self::$singletonInstances[$finalClassName])) {
+                       return self::$singletonInstances[$finalClassName];
+               }
+
+                       // Return instance if it has been injected by addInstance()
+               if (isset(self::$nonSingletonInstances[$finalClassName])
+                       && !empty(self::$nonSingletonInstances[$finalClassName])
+               ) {
+                       return array_shift(self::$nonSingletonInstances[$finalClassName]);
+               }
 
-                       // Get final classname
-               $className = self::getClassName($className);
+                       // Create new instance and call constructor with parameters
+               if (func_num_args() > 1) {
+                       $constructorArguments = func_get_args();
+                       array_shift($constructorArguments);
 
-               if (isset($instances[$className])) {
-                               // it's a singleton, get the existing instance
-                       $instance = $instances[$className];
+                       $reflectedClass = new ReflectionClass($finalClassName);
+                       $instance = $reflectedClass->newInstanceArgs($constructorArguments);
                } else {
-                       if (func_num_args() > 1) {
-                                       // getting the constructor arguments by removing this
-                                       // method's first argument (the class name)
-                               $constructorArguments = func_get_args();
-                               array_shift($constructorArguments);
-
-                               $reflectedClass = new ReflectionClass($className);
-                               $instance = $reflectedClass->newInstanceArgs($constructorArguments);
-                       } else {
-                               $instance = new $className;
-                       }
+                       $instance = new $finalClassName;
+               }
 
-                       if ($instance instanceof t3lib_Singleton) {
-                                       // it's a singleton, save the instance for later reuse
-                               $instances[$className] = $instance;
-                       }
+                       // Register new singleton instance
+               if ($instance instanceof t3lib_Singleton) {
+                       self::$singletonInstances[$finalClassName] = $instance;
                }
 
                return $instance;
@@ -5423,6 +5446,97 @@ final class t3lib_div {
                return (class_exists($className) && class_exists('ux_' . $className, FALSE) ? self::getClassName('ux_' . $className) : $className);
        }
 
+       /**
+        * Sets the instance of a singleton class to be returned by makeInstance.
+        *
+        * If this function is called multiple times for the same $className,
+        * makeInstance will return the last set instance.
+        *
+        * Warning: This is a helper method for unit tests. Do not call this directly in production code!
+        *
+        * @see makeInstance
+        * @param string $className
+        *        the name of the class to set, must not be empty
+        * @param t3lib_Singleton $instance
+        *        the instance to set, must be an instance of $className
+        * @return void
+        */
+       public static function setSingletonInstance($className, t3lib_Singleton $instance) {
+               self::checkInstanceClassName($className, $instance);
+               self::$singletonInstances[$className] = $instance;
+       }
+
+       /**
+        * Sets the instance of a non-singleton class to be returned by makeInstance.
+        *
+        * If this function is called multiple times for the same $className,
+        * makeInstance will return the instances in the order in which they have
+        * been added (FIFO).
+        *
+        * Warning: This is a helper method for unit tests. Do not call this directly in production code!
+        *
+        * @see makeInstance
+        * @throws InvalidArgumentException if class extends t3lib_Singleton
+        * @param string $className
+        *        the name of the class to set, must not be empty
+        * @param object $instance
+        *        the instance to set, must be an instance of $className
+        * @return void
+        */
+       public static function addInstance($className, $instance) {
+               self::checkInstanceClassName($className, $instance);
+
+               if ($instance instanceof t3lib_Singleton) {
+                       throw new InvalidArgumentException(
+                               '$instance must not be an instance of t3lib_Singleton. ' .
+                                       'For setting singletons, please use setSingletonInstance.',
+                               1288969325
+                       );
+               }
+
+               if (!isset(self::$nonSingletonInstances[$className])) {
+                       self::$nonSingletonInstances[$className] = array();
+               }
+               self::$nonSingletonInstances[$className][] = $instance;
+       }
+
+       /**
+        * Checks that $className is non-empty and that $instance is an instance of
+        * $className.
+        *
+        * @throws InvalidArgumentException if $className is empty or if $instance is no instance of $className
+        * @param string $className a class name
+        * @param object $instance an object
+        * @return void
+        */
+       protected static function checkInstanceClassName($className, $instance) {
+               if ($className === '') {
+                       throw new InvalidArgumentException('$className must not be empty.', 1288967479);
+               }
+               if (!($instance instanceof $className)) {
+                       throw new InvalidArgumentException(
+                               '$instance must be an instance of ' . $className . ', but actually is an instance of ' . get_class($instance) . '.',
+                               1288967686
+                       );
+               }
+       }
+
+       /**
+        * Purge all instances returned by makeInstance.
+        *
+        * This function is most useful when called from tearDown in a testcase
+        * to drop any instances that have been created by the tests.
+        *
+        * Warning: This is a helper method for unit tests. Do not call this directly in production code!
+        *
+        * @see makeInstance
+        * @return void
+        */
+       public static function purgeInstances() {
+               self::$singletonInstances = array();
+               self::$nonSingletonInstances = array();
+       }
+
        /**
         * Find the best service and check if it works.
         * Returns object of the service class.
@@ -6210,4 +6324,4 @@ final class t3lib_div {
        }
 }
 
-?>
\ No newline at end of file
+?>
index 0b6ffea..8c68215 100644 (file)
@@ -48,6 +48,10 @@ class t3lib_divTest extends tx_phpunit_testcase {
         */
        protected $backupGlobalsBlacklist = array('TYPO3_DB');
 
+       public function tearDown() {
+               t3lib_div::purgeInstances();
+       }
+
 
        ///////////////////////////////
        // Tests concerning validIP
@@ -1816,9 +1820,18 @@ class t3lib_divTest extends tx_phpunit_testcase {
        }
 
 
-       //////////////////////////////////
-       // Tests concerning makeInstance
-       //////////////////////////////////
+       /////////////////////////////////////////////////////////////////////////////////////
+       // Tests concerning makeInstance, setSingletonInstance, addInstance, purgeInstances
+       /////////////////////////////////////////////////////////////////////////////////////
+
+       /**
+        * @test
+        *
+        * @expectedException InvalidArgumentException
+        */
+       public function makeInstanceWithEmptyClassNameThrowsException() {
+               t3lib_div::makeInstance('');
+       }
 
        /**
         * @test
@@ -1886,5 +1899,173 @@ class t3lib_divTest extends tx_phpunit_testcase {
                        t3lib_div::makeInstance($className)
                );
        }
+
+       /**
+        * @test
+        */
+       public function makeInstanceCalledTwoTimesForSingletonClassWithPurgeInstancesInbetweenReturnsDifferentInstances() {
+               $className = get_class($this->getMock('t3lib_Singleton'));
+
+               $instance = t3lib_div::makeInstance($className);
+               t3lib_div::purgeInstances();
+
+               $this->assertNotSame(
+                       $instance,
+                       t3lib_div::makeInstance($className)
+               );
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function setSingletonInstanceForEmptyClassNameThrowsException() {
+               $instance = $this->getMock('t3lib_Singleton');
+
+               t3lib_div::setSingletonInstance('', $instance);
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function setSingletonInstanceForClassThatIsNoSubclassOfProvidedClassThrowsException() {
+               $instance = $this->getMock('t3lib_Singleton', array('foo'));
+               $singletonClassName = get_class($this->getMock('t3lib_Singleton'));
+
+               t3lib_div::setSingletonInstance($singletonClassName, $instance);
+       }
+
+       /**
+        * @test
+        */
+       public function setSingletonInstanceMakesMakeInstanceReturnThatInstance() {
+               $instance = $this->getMock('t3lib_Singleton');
+               $singletonClassName = get_class($instance);
+
+               t3lib_div::setSingletonInstance($singletonClassName, $instance);
+
+               $this->assertSame(
+                       $instance,
+                       t3lib_div::makeInstance($singletonClassName)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function setSingletonInstanceCalledTwoTimesMakesMakeInstanceReturnLastSetInstance() {
+               $instance1 = $this->getMock('t3lib_Singleton');
+               $singletonClassName = get_class($instance1);
+               $instance2 = new $singletonClassName();
+
+               t3lib_div::setSingletonInstance($singletonClassName, $instance1);
+               t3lib_div::setSingletonInstance($singletonClassName, $instance2);
+
+               $this->assertSame(
+                       $instance2,
+                       t3lib_div::makeInstance($singletonClassName)
+               );
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function addInstanceForEmptyClassNameThrowsException() {
+               $instance = $this->getMock('foo');
+
+               t3lib_div::addInstance('', $instance);
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function addInstanceForClassThatIsNoSubclassOfProvidedClassThrowsException() {
+               $instance = $this->getMock('foo', array('bar'));
+               $singletonClassName = get_class($this->getMock('foo'));
+
+               t3lib_div::addInstance($singletonClassName, $instance);
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function addInstanceWithSingletonInstanceThrowsException() {
+               $instance = $this->getMock('t3lib_Singleton');
+
+               t3lib_div::addInstance(get_class($instance), $instance);
+       }
+
+       /**
+        * @test
+        */
+       public function addInstanceMakesMakeInstanceReturnThatInstance() {
+               $instance = $this->getMock('foo');
+               $className = get_class($instance);
+
+               t3lib_div::addInstance($className, $instance);
+
+               $this->assertSame(
+                       $instance,
+                       t3lib_div::makeInstance($className)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function makeInstanceCalledTwoTimesAfterAddInstanceReturnTwoDifferentInstances() {
+               $instance = $this->getMock('foo');
+               $className = get_class($instance);
+
+               t3lib_div::addInstance($className, $instance);
+
+               $this->assertNotSame(
+                       t3lib_div::makeInstance($className),
+                       t3lib_div::makeInstance($className)
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function addInstanceCalledTwoTimesMakesMakeInstanceReturnBothInstancesInAddingOrder() {
+               $instance1 = $this->getMock('foo');
+               $className = get_class($instance1);
+               t3lib_div::addInstance($className, $instance1);
+
+               $instance2 = new $className();
+               t3lib_div::addInstance($className, $instance2);
+
+               $this->assertSame(
+                       $instance1,
+                       t3lib_div::makeInstance($className),
+                       'The first returned instance does not match the first added instance.'
+               );
+               $this->assertSame(
+                       $instance2,
+                       t3lib_div::makeInstance($className),
+                       'The second returned instance does not match the second added instance.'
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function purgeInstancesDropsAddedInstance() {
+               $instance = $this->getMock('foo');
+               $className = get_class($instance);
+
+               t3lib_div::addInstance($className, $instance);
+               t3lib_div::purgeInstances();
+
+               $this->assertNotSame(
+                       $instance,
+                       t3lib_div::makeInstance($className)
+               );
+       }
 }
-?>
\ No newline at end of file
+?>