[FEATURE] Bootstrap: Introduce shutdown and use in autoloader
authorChristian Kuhn <lolli@schwarzbu.ch>
Thu, 17 May 2012 20:23:57 +0000 (22:23 +0200)
committerSusanne Moog <typo3@susannemoog.de>
Sun, 20 May 2012 10:52:26 +0000 (12:52 +0200)
The patch introduces a shutdown method to the bootstrap that
can be used to perform clean up and shutdown things at the
end of a script run. It is used in the most important entry
scripts like FE, mod.php, cli and backend.

First usage of shutdown() is the autoloader: If new classes are found
during script run, for example because they stick to the extbase naming
scheme and do not have a ext_autoload entry, the autoloader wrote a new
cache file for each class it found. Now, the autoloader remembers that
the cache file should be updated and delays of writing the cache file
until it is unregistered.
This way the full cache entry is only written once during shutdown.

Change-Id: Ibb84a7db65eb28e792c0b5ee9bedec00b29dbbb9
Resolves: #37268
Releases: 6.0
Reviewed-on: http://review.typo3.org/11279
Reviewed-by: Philipp Gampe
Tested-by: Philipp Gampe
Reviewed-by: Oliver Klee
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Susanne Moog
Tested-by: Susanne Moog
t3lib/class.t3lib_autoloader.php
tests/Unit/t3lib/class.t3lib_autoloaderTest.php
typo3/Bootstrap.php
typo3/backend.php
typo3/cli_dispatch.phpsh
typo3/mod.php
typo3/sysext/cms/tslib/index_ts.php

index f641ae0..3d05830 100644 (file)
@@ -56,6 +56,17 @@ class t3lib_autoloader {
        protected static $autoloadCacheIdentifier = NULL;
 
        /**
+        * Track if the cache file written to disk should be updated.
+        * This is set to TRUE if during script run new classes are
+        * found (for example due to new requested extbase classes)
+        * and is used in unregisterAutoloader() to decide whether or not
+        * the cache file should be re-written.
+        *
+        * @var bool True if mapping changed
+        */
+       protected static $cacheUpdateRequired = FALSE;
+
+       /**
         * The autoloader is static, thus we do not allow instances of this class.
         */
        private function __construct() {
@@ -72,12 +83,20 @@ class t3lib_autoloader {
        }
 
        /**
-        * Uninstalls TYPO3 autoloader. This function is for the sake of completeness.
-        * It is never called by the TYPO3 core.
+        * Uninstalls TYPO3 autoloader and writes any additional classes
+        * found during the script run to the cache file.
+        *
+        * This method is called during shutdown of the framework.
         *
         * @return boolean TRUE in case of success
         */
        public static function unregisterAutoloader() {
+               if (self::$cacheUpdateRequired) {
+                       self::updateRegistryCacheEntry(self::$classNameToFileMapping);
+                       self::$cacheUpdateRequired = FALSE;
+               }
+               self::$classNameToFileMapping = array();
+
                return spl_autoload_unregister('t3lib_autoloader::autoload');
        }
 
@@ -119,25 +138,25 @@ class t3lib_autoloader {
                $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_phpcode');
 
                        // Create autoload cache file if it does not exist yet
-               if (!$phpCodeCache->has(self::getAutoloadCacheIdentifier())) {
+               if ($phpCodeCache->has(self::getAutoloadCacheIdentifier())) {
+                       $classRegistry = $phpCodeCache->requireOnce(self::getAutoloadCacheIdentifier());
+               } else {
+                       self::$cacheUpdateRequired = TRUE;
                        $classRegistry = self::createCoreAndExtensionRegistry();
-                       self::updateRegistryCacheEntry($classRegistry);
                }
 
-                       // Require calculated cache file
-               $mappingArray = $phpCodeCache->requireOnce(self::getAutoloadCacheIdentifier());
-
                        // This can only happen if the autoloader was already registered
                        // in the same call once, the requireOnce of the cache file then
                        // does not give the cached array back. In this case we just read
                        // all cache entries manually again.
                        // This can happen in unit tests and if the cache backend was
                        // switched to NullBackend for example to simplify development
-               if (!is_array($mappingArray)) {
-                       $mappingArray = self::createCoreAndExtensionRegistry();
+               if (!is_array($classRegistry)) {
+                       self::$cacheUpdateRequired = TRUE;
+                       $classRegistry = self::createCoreAndExtensionRegistry();
                }
 
-               self::$classNameToFileMapping = $mappingArray;
+               self::$classNameToFileMapping = $classRegistry;
        }
 
        /**
@@ -310,8 +329,8 @@ class t3lib_autoloader {
         */
        protected static function addClassToCache($classFilePathAndName, $className) {
                if (file_exists($classFilePathAndName)) {
+                       self::$cacheUpdateRequired = TRUE;
                        self::$classNameToFileMapping[strtolower($className)] = $classFilePathAndName;
-                       self::updateRegistryCacheEntry(self::$classNameToFileMapping);
                }
        }
 
index aae0643..1fa3a5d 100644 (file)
@@ -111,7 +111,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
-       public function UnregisterAndRegisterAgainDoesNotFatal() {
+       public function unregisterAndRegisterAgainDoesNotFatal() {
                t3lib_autoloader::unregisterAutoloader();
                t3lib_autoloader::registerAutoloader();
                        // If this fatals the autoload re registering went wrong
@@ -121,7 +121,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
-       public function registerSetsCacheEntryWithT3libAutoloaderTag() {
+       public function unregisterAutoloaderSetsCacheEntryWithT3libAutoloaderTag() {
                $mockCache = $this->getMock('t3lib_cache_frontend_AbstractFrontend', array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'), array(), '', FALSE);
                        // Expect the mock cache set method to be called
                        // once with t3lib_autoloader as third parameter
@@ -129,7 +129,6 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
                $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();
        }
 
        /**
@@ -164,7 +163,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
-       public function autoloadWritesLowerCasedClassFileToCache() {
+       public function unregisterAutoloaderWritesLowerCasedClassFileToCache() {
                $extKey = $this->createFakeExtension();
                $extPath = PATH_site . "typo3temp/$extKey/";
                $autoloaderFile = $extPath . "ext_autoload.php";
@@ -182,11 +181,12 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
                $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
 
                        // Expect that the lower case version of the class name is written to cache
-               $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($class), FALSE));
+               $mockCache->expects($this->at(2))->method('set')->with($this->anything(), $this->stringContains(strtolower($class), FALSE));
 
                        // Re-initialize autoloader registry to force it to recognize the new extension
                t3lib_autoloader::unregisterAutoloader();
                t3lib_autoloader::registerAutoloader();
+               t3lib_autoloader::unregisterAutoloader();
        }
 
        /**
@@ -250,7 +250,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
-       public function autoloadWritesClassFileThatRespectsExtbaseNamingSchemeToCacheFile() {
+       public function unregisterAutoloaderWritesClassFileThatRespectsExtbaseNamingSchemeToCacheFile() {
                $extKey = $this->createFakeExtension();
                $extPath = PATH_site . "typo3temp/$extKey/";
 
@@ -270,12 +270,13 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
                $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($class), $this->anything()));
 
                t3lib_autoloader::autoload($class);
+               t3lib_autoloader::unregisterAutoloader();
        }
 
        /**
         * @test
         */
-       public function autoloadWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeToCacheFile() {
+       public function unregisterAutoloaderWritesClassFileLocationOfClassRespectingExtbaseNamingSchemeToCacheFile() {
                $extKey = $this->createFakeExtension();
                $extPath = PATH_site . "typo3temp/$extKey/";
 
@@ -295,64 +296,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
                $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains(strtolower($file), $this->anything()));
 
                t3lib_autoloader::autoload($class);
-       }
-
-       /**
-        * @test
-        */
-       public function autoloadDoesNotSetCacheEntryForClassThatRespectsExtbaseNamingSchemeOnConsecutiveCallsForSameClass() {
-               $extKey = $this->createFakeExtension();
-               $extPath = PATH_site . "typo3temp/$extKey/";
-
-               $pathSegment = 'Foo' . uniqid();
-               $fileName = 'Bar' . uniqid();
-               $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
-               $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
-
-               t3lib_div::mkdir_deep($extPath . 'Classes/' . $pathSegment);
-               file_put_contents($file, "<?php\n\n\$foo = 'bar';\n\n?>");
-
-               $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));
-
-                       // Expect the set method is called exactly once, even if the class is called multiple times.
-                       // This means that the internal array of the autoloader class is successfully used
-               $mockCache->expects($this->once())->method('set');
-
-               t3lib_autoloader::autoload($class);
-               t3lib_autoloader::autoload($class);
-       }
-
-       /**
-        * @test
-        */
-       public function autoloadReadsClassFileLocationFromCacheFileForClassThatRespectsExtbaseNamingScheme() {
-               $extKey = $this->createFakeExtension();
-               $extPath = PATH_site . "typo3temp/$extKey/";
-
-               $pathSegment = 'Foo' . uniqid();
-               $fileName = 'Bar' . uniqid();
-               $class = 'Tx_' . $extKey . '_' . $pathSegment . '_' . $fileName;
-               $file = $extPath . 'Classes/' . $pathSegment . '/' . $fileName . '.php';
-
-               t3lib_div::mkdir_deep($extPath . 'Classes/' . $pathSegment);
-               file_put_contents($file, "<?php\n\n\$foo = 'bar';\n\n?>");
-
-               $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));
-
-                       // Expect the set method is called exactly once, even if the class is called multiple times.
-                       // This means that the internal array of the autoloader class is successfully used
-               $mockCache->expects($this->once())
-                       ->method('requireOnce')
-                       ->will($this->returnValue(array(strtolower($class) => $file)));
-
-               t3lib_autoloader::autoload($class);
                t3lib_autoloader::unregisterAutoloader();
-               t3lib_autoloader::registerAutoloader();
-               t3lib_autoloader::autoload($class);
        }
 
        /**
@@ -416,7 +360,7 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
        /**
         * @test
         */
-       public function getClassPathByRegistryLookupAddFoundClassRegisteredInDeprecatedTypo3ConfVarsToCacheFileOnce() {
+       public function unregisterAutoloaderWritesDeprecatedTypo3ConfVarsRegisteredXclassClassFoundByGetClassPathByRegistryLookupToCache() {
                        // Create a fake extension
                $extKey = $this->createFakeExtension();
                $extPath = PATH_site . "typo3temp/$extKey/";
@@ -439,15 +383,14 @@ class t3lib_autoloaderTest extends Tx_Phpunit_TestCase {
                $GLOBALS['typo3CacheManager'] = $this->getMock('t3lib_cache_Manager', array('getCache'));
                $GLOBALS['typo3CacheManager']->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
 
-                       // Excpect the cache entry to be called exactly once
-               $mockCache->expects($this->exactly(2))->method('set');
+                       // 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));
 
                t3lib_autoloader::unregisterAutoloader();
                t3lib_autoloader::registerAutoloader();
 
-                       // Let the autoloader find the xclass to trigger cache access
-               t3lib_autoloader::getClassPathByRegistryLookup("ux_$class");
                t3lib_autoloader::getClassPathByRegistryLookup("ux_$class");
+               t3lib_autoloader::unregisterAutoloader();
        }
 }
 ?>
\ No newline at end of file
index 923fbf9..e77d501 100644 (file)
@@ -368,6 +368,17 @@ class Typo3_Bootstrap {
        }
 
        /**
+        * Things that should be performed to shut down the framework.
+        * This method is called in all important scripts for a clean
+        * shut down of the system.
+        *
+        * @return void
+        */
+       public static function shutdown() {
+               t3lib_autoloader::unregisterAutoloader();
+       }
+
+       /**
         * Check php version requirement or exit script
         *
         * @return void
index 751eb22..c92c919 100644 (file)
@@ -841,4 +841,6 @@ if (is_array($GLOBALS['TBE_MODULES']['_configuration'])) {
 
 $TYPO3backend->render();
 
+Typo3_Bootstrap::shutdown();
+
 ?>
index 31aacf0..82323f2 100755 (executable)
@@ -92,4 +92,6 @@ try {
        fwrite(STDERR, $e->getMessage() . LF);
        exit(99);
 }
+
+Typo3_Bootstrap::shutdown();
 ?>
\ No newline at end of file
index b65fa1e..9946300 100644 (file)
@@ -61,4 +61,6 @@ if ($isDispatched === FALSE) {
        throw new UnexpectedValueException('No module "' . htmlspecialchars($temp_M) . '" could be found.', 1294585070);
 }
 
+Typo3_Bootstrap::shutdown();
+
 ?>
index 23e78cb..96a1b95 100644 (file)
@@ -384,4 +384,6 @@ if (TYPO3_DLOG) {
        t3lib_div::devLog('END of FRONTEND session', 'cms', 0, array('_FLUSH' => TRUE));
 }
 
-?>
\ No newline at end of file
+Typo3_Bootstrap::shutdown();
+
+?>