[TASK] Introduce object implementation registry
authorHelmut Hummel <helmut.hummel@typo3.org>
Sat, 24 Nov 2012 17:48:28 +0000 (18:48 +0100)
committerHelmut Hummel <helmut.hummel@typo3.org>
Sun, 25 Nov 2012 11:20:17 +0000 (12:20 +0100)
With 6.0 all Xclass inclusion in the classes have been
removed and registration of Xclasses have previously been
moved to the autoloader. This added additional complexity
and another concern to the class loader.

To enable extensions to override classes, an object
implementation registry is now introduced, where you
can now define which implementation class name should
be used for an original class.

This not only is a complete replacement for former
Xclasses, but at the same time adds the possibility
to have the implementation class name within the namespace
and scope of the extension which registers the override.

On top it would also be possible to register implementations
for interfaces.

Since only class names are mapped here, we again have a
clear separation of concerns, where the class loader
is only responsible to resolve paths for class names
and the "object manager" method makeInstance for resolving
the final class name to instantiate.

The path of the target class can be resolved automatically
by the class loader if naming conventions are met or the
target class name is put into the autoloading registry.

An implementation of a class can be registered like this:

$TYPO3_CONF_VARS['SYS']['Objects']['originalClass'] =
  array('className' => 'targetClass');

The patch adjusts the ClassLoader and makeInstance.
The unit tests for the ClassLoader are removed since
it is not responsible for resolving Xclasses any more.

Instead unit tests for makeInstance are added to test
the new behavior.

Resolves: #43269
Releases: 6.0

Change-Id: I04198b178aa41f02d7a5b7ee990ff24f483990f5
Reviewed-on: http://review.typo3.org/16712
Reviewed-by: Steffen Ritter
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
Tested-by: Steffen Ritter
Reviewed-by: Christian Kuhn
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
t3lib/stddb/DefaultConfiguration.php
typo3/sysext/core/Classes/Compatibility/CompatbilityClassLoaderPhpBelow50307.php
typo3/sysext/core/Classes/Core/ClassLoader.php
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/core/Migrations/Code/LegacyClassesForIde.php
typo3/sysext/core/Tests/Unit/Compatibility/CompatbilityClassLoaderPhpBelow50307Test.php
typo3/sysext/core/Tests/Unit/Core/ClassLoaderTest.php
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php

index fead095..436d688 100644 (file)
@@ -190,6 +190,7 @@ return array(
                'belogErrorReporting' => E_ALL & ~(E_STRICT | E_NOTICE),                // Integer: Configures which PHP errors should be logged to the "syslog" table (extension: belog). If set to "0" no PHP errors are logged to the sys_log table. Default is "E_ALL ^ E_NOTICE" (6135).
                'locallangXMLOverride' => array(),              // For extension/overriding of the arrays in 'locallang' files in frontend and backend. See 'Inside TYPO3' for more information.
                'generateApacheHtaccess' => 1,          // Boolean: TYPO3 can create <em>.htaccess</em> files which are used by Apache Webserver. They are useful for access protection or performance improvements. Currently <em>.htaccess</em> files in the following directories are created, if they do not exist: <ul><li>typo3temp/compressor/</li></ul>You want to disable this feature, if you are not running Apache or want to use own rulesets.
+               'Objects' => array(),
                'fal' => array(
                        'registeredDrivers' => array(
                                'Local' => array(
index 2d670a2..8865d09 100644 (file)
@@ -78,7 +78,6 @@ class CompatbilityClassLoaderPhpBelow50307 extends \TYPO3\CMS\Core\Core\ClassLoa
                        GeneralUtility::isFirstPartOfStr($className, 'tx_')
                        || GeneralUtility::isFirstPartOfStr($className, 'Tx_')
                        || GeneralUtility::isFirstPartOfStr($className, 'ux_')
-                       || GeneralUtility::isFirstPartOfStr($className, 'Ux_')
                        || GeneralUtility::isFirstPartOfStr($className, 'user_')
                        || GeneralUtility::isFirstPartOfStr($className, 'User_')
                ) {
index 5213cc7..1d4de2e 100644 (file)
@@ -34,7 +34,6 @@ use \TYPO3\CMS\Core\Utility\GeneralUtility;
  * - The core of TYPO3
  * - All extensions with an ext_autoload.php file
  * - All extensions that stick to the 'extbase' like naming convention
- * - Resolves registered XCLASSes
  *
  * @author Dmitry Dulepov <dmitry@typo3.org>
  * @author Martin Kutschker <masi@typo3.org>
@@ -292,17 +291,14 @@ class ClassLoader {
                $classPath = NULL;
                $classNameLower = GeneralUtility::strtolower($className);
                // Try to resolve extbase naming scheme if class is not already in cache file
-               if (substr($classNameLower, 0, 3) !== 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
+               if (!array_key_exists($classNameLower, static::$classNameToFileMapping)) {
                        static::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
                }
                // Look up class name in cache file
                if (array_key_exists($classNameLower, static::$classNameToFileMapping)) {
                        $classPath = static::$classNameToFileMapping[$classNameLower];
                }
-               if ($classPath === NULL && substr($classNameLower, 0, 3) === 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
-                       static::$cacheUpdateRequired = TRUE;
-                       static::$classNameToFileMapping[$classNameLower] = NULL;
-               }
+
                return $classPath;
        }
 
index 86f8975..f2ebbfd 100644 (file)
@@ -4156,7 +4156,7 @@ Connection: close
                }
                // Create alias if not present
                $alias = \TYPO3\CMS\Core\Core\ClassLoader::getAliasForClassName($finalClassName);
-               if (substr($finalClassName, 0, 3) !== 'ux_' && $finalClassName !== $alias && !class_exists($alias, FALSE)) {
+               if ($finalClassName !== $alias && !class_exists($alias, FALSE)) {
                        class_alias($finalClassName, $alias);
                }
                // Register new singleton instance
@@ -4167,22 +4167,44 @@ Connection: close
        }
 
        /**
-        * Returns the class name for a new instance, taking into account the
-        * class-extension API.
+        * Returns the class name for a new instance, taking into account
+        * registered implemetations for this class
         *
         * @param string $className Base class name to evaluate
         * @return string Final class name to instantiate with "new [classname]
         */
        static protected function getClassName($className) {
                if (class_exists($className)) {
-                       while (\TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup('ux_' . $className) !== NULL) {
-                               $className = 'ux_' . $className;
+                       while (static::classHasImplementation($className)) {
+                               $className = static::geImplementationForClass($className);
                        }
                }
                return \TYPO3\CMS\Core\Core\ClassLoader::getClassNameForAlias($className);
        }
 
        /**
+        * Returns the confiured implementation of the class
+        *
+        * @param string $className
+        * @return string
+        */
+       static protected function geImplementationForClass($className) {
+               return $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className]['className'];
+       }
+
+       /**
+        * Checks if a class has a configured implementation
+        *
+        * @param string $className
+        * @return boolean
+        */
+       static protected function classHasImplementation($className) {
+               return array_key_exists($className, $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'])
+                               && is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$className])
+                               && !empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$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,
index 555394e..6fe4bac 100644 (file)
@@ -1464,12 +1464,12 @@ class tx_cssstyledcontent_pi1 extends \TYPO3\CMS\CssStyledContent\Controller\Css
 /**
  * @deprecated since 6.0 will be removed in 7.0
  */
-class ux_t3lib_DB extends \ux_TYPO3\CMS\Core\Database\DatabaseConnection {}
+class ux_t3lib_DB extends \TYPO3\CMS\Dbal\Database\DatabaseConnection {}
 
 /**
  * @deprecated since 6.0 will be removed in 7.0
  */
-class ux_t3lib_sqlparser extends \ux_TYPO3\CMS\Core\Database\SqlParser {}
+class ux_t3lib_sqlparser extends \TYPO3\CMS\Dbal\Database\SqlParser {}
 
 /**
  * @deprecated since 6.0 will be removed in 7.0
@@ -1504,7 +1504,7 @@ class tx_dbal_querycache extends \TYPO3\CMS\Dbal\QueryCache {}
 /**
  * @deprecated since 6.0 will be removed in 7.0
  */
-class ux_localRecordList extends \ux_TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList {}
+class ux_localRecordList extends \TYPO3\CMS\Dbal\RecordList\DatabaseRecordList {}
 
 /**
  * @deprecated since 6.0 will be removed in 7.0
@@ -6680,4 +6680,4 @@ class Tx_Workspaces_Service_AutoPublishTask extends \TYPO3\CMS\Workspaces\Task\A
  * @deprecated since 6.0 will be removed in 7.0
  */
 class Tx_Workspaces_Service_CleanupPreviewLinkTask extends \TYPO3\CMS\Workspaces\Task\CleanupPreviewLinkTask {}
-?>
+?>
\ No newline at end of file
index 7a17f1d..a881b70 100644 (file)
@@ -89,7 +89,6 @@ class CompatbilityClassLoaderPhpBelow50307Test extends \TYPO3\CMS\Core\Tests\Uni
                        'user_' => array('user_fooBar'),
                        'User_' => array('User_Barfoo'),
                        'ux_' => array('ux_Foo'),
-                       'Ux_' => array('Ux_bar'),
                );
        }
 
index 10968a2..5f70b73 100644 (file)
@@ -283,77 +283,6 @@ class ClassLoaderTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
 
        /**
         * @test
-        */
-       public function getClassPathByRegistryLookupFindsClassPrefixedWithUxRegisteredInExtAutoloadFile() {
-                       // Create a dummy extension with a path to a 'ux_' prefixed php file
-               $extKey = $this->createFakeExtension();
-               $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
-               $autoloaderFile = $extPath . 'ext_autoload.php';
-               $class = strtolower('ux_tx_{' . $extKey . '}_' . uniqid(''));
-               $file = $extPath . uniqid('') . '.php';
-               file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
-                       // Inject a dummy for the core_phpcode cache to force the autoloader
-                       // to re calculate the registry
-               $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
-               $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
-               $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-                       // Re-initialize autoloader registry to force it to recognize the new extension with the ux_ autoload definition
-               \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
-               \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
-               $this->assertSame($file, \TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup($class));
-       }
-
-       /**
-        * @test
-        */
-       public function unregisterAutoloaderWritesNotExistingUxCLassLookupFromGetClassPathByRegistryLookupToCache() {
-               $uxClassName = 'ux_Tx_Foo' . uniqid();
-               $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
-               $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
-               $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-               \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
-               \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
-                       // Class is not found by returning NULL
-               $this->assertSame(NULL, \TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup($uxClassName));
-               // Expect NULL lookup is cached
-               $expectedCacheString = '\'' . strtolower($uxClassName) . '\' => NULL,';
-               $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains($expectedCacheString));
-                       // Trigger writing new cache file
-               \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
-       }
-
-       /**
-        * @test
-        */
-       public function unregisterAutoloaderWritesDeprecatedTypo3ConfVarsRegisteredXclassClassFoundByGetClassPathByRegistryLookupToCache() {
-                       // Create a fake extension
-               $extKey = $this->createFakeExtension();
-               $extPath = PATH_site . 'typo3temp/' . $extKey . '/';
-               $autoloaderFile = $extPath . 'ext_autoload.php';
-                       // Feed ext_autoload with a base file and the class file
-               $class = strtolower('tx_{' . $extKey . '}_' . uniqid(''));
-               $fileName = uniqid('') . '.php';
-               $file = $extPath . $fileName;
-               $xClassFile = 'typo3temp/' . $extKey . '/xclassFile';
-               file_put_contents($autoloaderFile, '<?php' . LF . 'return array(\'' . $class . '\' => \'' . $file . '\');' . LF . '?>');
-               file_put_contents(PATH_site . $xClassFile, '<?php' . LF . 'die();' . LF . '?>');
-                       // Register a xclass for the base file
-               $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['typo3temp/' . $extKey . '/' . $fileName] = $xClassFile;
-                       // Inject a dummy for the core_phpcode cache to force the autoloader
-                       // to re calculate the registry
-               $mockCache = $this->getMock('TYPO3\\CMS\\Core\\Cache\\Frontend\\AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
-               $GLOBALS['typo3CacheManager'] = $this->getMock('TYPO3\\CMS\\Core\\Cache\\CacheManager', array('getCache'));
-               $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-                       // Excpect the cache entry to be called once with the new class name
-               $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains('ux_' . $class));
-               \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
-               \TYPO3\CMS\Core\Core\ClassLoader::registerAutoloader();
-               \TYPO3\CMS\Core\Core\ClassLoader::getClassPathByRegistryLookup('ux_' . $class);
-               \TYPO3\CMS\Core\Core\ClassLoader::unregisterAutoloader();
-       }
-
-       /**
-        * @test
         * @expectedException \RuntimeException
         */
        public function autoloadFindsClassFileThatRespectsExtbaseNamingSchemeWithNamespace() {
index c51a4bc..e531d84 100644 (file)
@@ -3531,6 +3531,28 @@ class GeneralUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
+       public function makeInstanceInstanciatesConfiguredImplementation() {
+               $classNameOriginal = get_class($this->getMock(uniqid('foo')));
+               $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$classNameOriginal] = array('className' => $classNameOriginal . 'Other');
+               eval('class ' . $classNameOriginal . 'Other extends ' . $classNameOriginal . ' {}');
+               $this->assertInstanceOf($classNameOriginal . 'Other', Utility\GeneralUtility::makeInstance($classNameOriginal));
+       }
+
+       /**
+        * @test
+        */
+       public function makeInstanceResolvesConfiguredImplementationsRecursively() {
+               $classNameOriginal = get_class($this->getMock(uniqid('foo')));
+               $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$classNameOriginal] = array('className' => $classNameOriginal . 'Other');
+               $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][$classNameOriginal . 'Other'] = array('className' => $classNameOriginal . 'OtherOther');
+               eval('class ' . $classNameOriginal . 'Other extends ' . $classNameOriginal . ' {}');
+               eval('class ' . $classNameOriginal . 'OtherOther extends ' . $classNameOriginal . 'Other {}');
+               $this->assertInstanceOf($classNameOriginal . 'OtherOther', Utility\GeneralUtility::makeInstance($classNameOriginal));
+       }
+
+       /**
+        * @test
+        */
        public function makeInstanceCalledTwoTimesForNonSingletonClassReturnsDifferentInstances() {
                $className = get_class($this->getMock('foo'));
                $this->assertNotSame(Utility\GeneralUtility::makeInstance($className), Utility\GeneralUtility::makeInstance($className));