[FEATURE] Cache negative ux_* look up in autoloader
authorChristian Kuhn <lolli@schwarzbu.ch>
Mon, 11 Jun 2012 19:16:21 +0000 (21:16 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sat, 23 Jun 2012 17:30:00 +0000 (19:30 +0200)
For every class instance that is requested via
t3lib_div::makeInstance(), a lookup for ux_"classname" is done
and that class is instantiated if it exists, instead of the original
class. If no ux_ class is found with class_exists(), the requested class
is instantiated. This is the basic "XCLASS" handling in the core.

The patch adds autoloader rows for unsuccessful (NULL) ux_* lookups to the
autoloader cache file. The file is updated during shutdown. This way the
information a XCLASS does NOT exist is cached between consecutive calls.
This leads to a performance improvement and is an advantage over the
current makeInstance class name cache that worked only for one call.
The old "cache for one call only" code is dropped with the patch.

Change-Id: I70573146600d658d218dc9b31694748341fc7a20
Resolves: #37915
Releases: 6.0
Reviewed-on: http://review.typo3.org/11943
Reviewed-by: Susanne Moog
Tested-by: Susanne Moog
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
t3lib/cache/class.t3lib_cache_factory.php
t3lib/class.t3lib_autoloader.php
t3lib/class.t3lib_cache.php
t3lib/class.t3lib_div.php
tests/Unit/t3lib/class.t3lib_autoloaderTest.php

index 6036886..ba8191b 100644 (file)
@@ -80,11 +80,9 @@ class t3lib_cache_Factory implements t3lib_Singleton {
         * @api
         */
        public function create($cacheIdentifier, $cacheObjectName, $backendObjectName, array $backendOptions = array()) {
-                       // This class is required so early during bootstrap that makeInstance()
-                       // can not be used, so new operator is used directly and the class
-                       // lookup entry is added manually to the makeInstanceCache
+                       // New operator used on purpose: This class is required early during
+                       // bootstrap before makeInstance() is propely set up
                $backend = new $backendObjectName($this->context, $backendOptions);
-               t3lib_div::addClassNameToMakeInstanceCache($backendObjectName, $backendObjectName);
 
                if (!$backend instanceof t3lib_cache_backend_Backend) {
                        throw new t3lib_cache_exception_InvalidBackend(
@@ -96,9 +94,8 @@ class t3lib_cache_Factory implements t3lib_Singleton {
                        $backend->initializeObject();
                }
 
-                       // new used on purpose, see comment above
+                       // New used on purpose, see comment above
                $cache = new $cacheObjectName($cacheIdentifier, $backend);
-               t3lib_div::addClassNameToMakeInstanceCache($cacheObjectName, $cacheObjectName);
 
                if (!$cache instanceof t3lib_cache_frontend_Frontend) {
                        throw new t3lib_cache_exception_InvalidCache(
index 747d19b..9f689d4 100644 (file)
@@ -83,7 +83,7 @@ class t3lib_autoloader {
        }
 
        /**
-        * Uninstalls TYPO3 autoloader and writes any additional classes
+        * Unload TYPO3 autoloader and write any additional classes
         * found during the script run to the cache file.
         *
         * This method is called during shutdown of the framework.
@@ -135,6 +135,7 @@ class t3lib_autoloader {
         * @return void
         */
        protected static function loadCoreAndExtensionRegistry() {
+               /** @var $phpCodeCache t3lib_cache_frontend_PhpFrontend */
                $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_phpcode');
 
                        // Create autoload cache file if it does not exist yet
@@ -182,10 +183,19 @@ class t3lib_autoloader {
                        // Look up class name in cache file
                if (array_key_exists($classNameLower, self::$classNameToFileMapping)) {
                        $classPath = self::$classNameToFileMapping[$classNameLower];
+               } else {
+                               // Handle deprecated XCLASS lookups
+                       $classPath = self::classPathForDeprecatedXclassHandling($classPath, $classNameLower);
                }
 
-                       // Handle deprecated XCLASS lookups
-               $classPath = self::classPathForDeprecatedXclassHandling($classPath, $classNameLower);
+               if (
+                       $classPath === NULL
+                       && substr($classNameLower, 0, 3) === 'ux_'
+                       && !array_key_exists($classNameLower, self::$classNameToFileMapping)
+               ) {
+                       self::$cacheUpdateRequired = TRUE;
+                       self::$classNameToFileMapping[$classNameLower] = NULL;
+               }
 
                return $classPath;
        }
@@ -344,7 +354,8 @@ class t3lib_autoloader {
        protected static function updateRegistryCacheEntry(array $registry) {
                $cachedFileContent = 'return array(';
                foreach ($registry as $className => $classLocation) {
-                       $cachedFileContent .= LF . '\'' . strtolower($className) . '\' => \'' . $classLocation . '\',';
+                       $nullOrLocation = is_string($classLocation) ? '\'' . $classLocation . '\',' : 'NULL,';
+                       $cachedFileContent .= LF . '\'' . strtolower($className) . '\' => ' . $nullOrLocation;
                }
                $cachedFileContent .= LF . ');';
                $GLOBALS['typo3CacheManager']->getCache('cache_phpcode')->set(
index 8392b21..d92077e 100644 (file)
@@ -47,12 +47,10 @@ class t3lib_cache {
                                // New operator used on purpose, makeInstance() is not ready to be used so early in bootstrap
                        $GLOBALS['typo3CacheManager'] = new t3lib_cache_Manager();
                        t3lib_div::setSingletonInstance('t3lib_cache_Manager', $GLOBALS['typo3CacheManager']);
-                       t3lib_div::addClassNameToMakeInstanceCache('t3lib_cache_Manager', 't3lib_cache_Manager');
                        $GLOBALS['typo3CacheManager']->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
                                // New operator used on purpose, makeInstance() is not ready to be used so early in bootstrap
                        $GLOBALS['typo3CacheFactory'] = new t3lib_cache_Factory('production', $GLOBALS['typo3CacheManager']);
                        t3lib_div::setSingletonInstance('t3lib_cache_Factory', $GLOBALS['typo3CacheFactory']);
-                       t3lib_div::addClassNameToMakeInstanceCache('t3lib_cache_Factory', 't3lib_cache_Factory');
                        self::$isCachingFrameworkInitialized = TRUE;
                }
        }
index 5b9de23..feb87ce 100644 (file)
@@ -65,13 +65,6 @@ final class t3lib_div {
         */
        protected static $nonSingletonInstances = array();
 
-       /**
-        * Register for makeInstance with given class name and final class names to reduce number of class_exists() calls
-        *
-        * @var array Given class name => final class name
-        */
-       protected static $finalClassNameRegister = array();
-
        /*************************
         *
         * GET/POST Variables
@@ -4488,12 +4481,7 @@ final class t3lib_div {
                        throw new InvalidArgumentException('$className must be a non empty string.', 1288965219);
                }
 
-                       // Determine final class name which must be instantiated, this takes XCLASS handling
-                       // into account. Cache in a local array to save some cycles for consecutive calls.
-               if (!isset(self::$finalClassNameRegister[$className])) {
-                       self::addClassNameToMakeInstanceCache($className, self::getClassName($className));
-               }
-               $finalClassName = self::$finalClassNameRegister[$className];
+               $finalClassName = self::getClassName($className);
 
                        // Return singleton instance if it is already registered
                if (isset(self::$singletonInstances[$finalClassName])) {
@@ -4565,22 +4553,6 @@ final class t3lib_div {
        }
 
        /**
-        * Adds a $className / $finalClassName to the cache register.
-        * This register is used to determine the final class name only once instead of multiple times.
-        *
-        * Warning: This is _not_ a public API method and must not be used in own extensions!
-        *
-        * @see makeInstance
-        * @param string $className the name of the class to set, must not be empty
-        * @param string $finalClassName the name of the final class which will be loaded in case of $className
-        * @return void
-        * @internal
-        */
-       public static function addClassNameToMakeInstanceCache($className, $finalClassName) {
-               self::$finalClassNameRegister[$className] = $finalClassName;
-       }
-
-       /**
         * Sets the instance of a non-singleton class to be returned by makeInstance.
         *
         * If this function is called multiple times for the same $className,
index 5f0ec0b..2dac80a 100644 (file)
@@ -360,6 +360,30 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
+       public function unregisterAutoloaderWritesNotExistingUxCLassLookupFromGetClassPathByRegistryLookupToCache() {
+               $uxClassName = 'ux_Tx_Foo' . uniqid();
+
+               $mockCache = $this->getMock('t3lib_cache_frontend_AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
+               $GLOBALS['typo3CacheManager'] = $this->getMock('t3lib_cache_Manager', array('getCache'));
+               $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
+
+               t3lib_autoloader::unregisterAutoloader();
+               t3lib_autoloader::registerAutoloader();
+
+                       // Class is not found by returning NULL
+               $this->assertSame(NULL, t3lib_autoloader::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
+               t3lib_autoloader::unregisterAutoloader();
+       }
+
+       /**
+        * @test
+        */
        public function unregisterAutoloaderWritesDeprecatedTypo3ConfVarsRegisteredXclassClassFoundByGetClassPathByRegistryLookupToCache() {
                        // Create a fake extension
                $extKey = $this->createFakeExtension();