[FEATURE] Allow XCLASSes to be defined via ext_autoload.php
authorBenjamin Mack <benni@typo3.org>
Thu, 17 Nov 2011 19:23:48 +0000 (20:23 +0100)
committerJigal van Hemert <jigal@xs4all.nl>
Mon, 16 Apr 2012 20:32:42 +0000 (22:32 +0200)
For XCLASSes to work currently, one needs to

a) set the according XCLASS statement in every PHP file that
   can/should be subclassed
b) use this XCLASS statement in an extensions' ext_localconf.php
   to include the subclass statement

This process is somehow unflexible, requires more code, and since
it is missing for some classes, it's also a hassle to maintain.

The attached change does the following:
1) Use the autoloader to search for classes prefixed by "ux_*"
2) Provide a backwards compatibility layer

TODO: Provide a possibility for BE admins to see which XCLASSes
are currently in use with this new method.

Change-Id: I4af4116108fecd9df0874c3e07e10b86f74d556e
Resolves: #31893
Releases: 6.0
Reviewed-on: http://review.typo3.org/6717
Reviewed-by: Tolleiv Nietsch
Tested-by: Tolleiv Nietsch
Reviewed-by: Jigal van Hemert
Tested-by: Jigal van Hemert
t3lib/cache/class.t3lib_cache_factory.php
t3lib/class.t3lib_autoloader.php
t3lib/class.t3lib_cache.php
t3lib/class.t3lib_div.php
t3lib/config_default.php

index d9f6055..3f1f939 100644 (file)
@@ -80,7 +80,8 @@ class t3lib_cache_Factory implements t3lib_Singleton {
         * @api
         */
        public function create($cacheIdentifier, $cacheObjectName, $backendObjectName, array $backendOptions = array()) {
-               $backend = t3lib_div::makeInstance($backendObjectName, $this->context, $backendOptions);
+               $backend = new $backendObjectName($this->context, $backendOptions);
+               t3lib_div::addClassNameToMakeInstanceCache($backendObjectName, $backendObjectName);
                if (!$backend instanceof t3lib_cache_backend_Backend) {
                        throw new t3lib_cache_exception_InvalidBackend(
                                '"' . $backendObjectName . '" is not a valid cache backend object.',
@@ -91,7 +92,8 @@ class t3lib_cache_Factory implements t3lib_Singleton {
                        $backend->initializeObject();
                }
 
-               $cache = t3lib_div::makeInstance($cacheObjectName, $cacheIdentifier, $backend);
+               $cache = new $cacheObjectName($cacheIdentifier, $backend);
+               t3lib_div::addClassNameToMakeInstanceCache($cacheObjectName, $cacheObjectName);
                if (!$cache instanceof t3lib_cache_frontend_Frontend) {
                        throw new t3lib_cache_exception_InvalidCache(
                                '"' . $cacheObjectName . '" is not a valid cache frontend object.',
index ee56b77..e1c45ac 100644 (file)
@@ -94,7 +94,9 @@ class t3lib_autoloader {
                $classPath = self::getClassPathByRegistryLookup($className);
 
                if ($classPath) {
+                               // include the required file that holds the class
                        t3lib_div::requireFile($classPath);
+
                } else {
                        try {
                                        // Regular expression for a valid classname taken from
@@ -145,15 +147,125 @@ class t3lib_autoloader {
         * @param string $className Class name to find source file of
         * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
         */
-       protected static function getClassPathByRegistryLookup($className) {
+       public static function getClassPathByRegistryLookup($className) {
                $classPath = NULL;
                $classNameLower = strtolower($className);
+
                if (!array_key_exists($classNameLower, self::$classNameToFileMapping)) {
                        self::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
                }
+
                if (array_key_exists($classNameLower, self::$classNameToFileMapping)) {
                        $classPath = self::$classNameToFileMapping[$classNameLower];
                }
+
+               /***************************************************************************
+                * The following source is only to support the old way of XCLASS inclusions
+                *
+                * Backwards-compatibility layer to include old XCLASS statements
+                * that are set via TYPO3_CONF_VARS[TYPO3_MODE][relativePathToFile] = extPath
+                **************************************************************************/
+                       // @todo Add ALL classes (like EXT:about/mod/index.php to the autoloader
+                       // Problem: EXT:about cannot be XCLASSed because the autoloader is not able to find the right path
+                       // Solution:
+                       //              - typo3/sysext/about/mod/index.php must be added to typo3/sysext/about/mod/ext_autoload.php
+                       //              - Have a look at the end of typo3/sysext/about/mod/index.php for the right XCLASS inclusion code
+                       //                      and replace it in this autoload logic. Have a look where is $relativeClassPath build
+                       //                      e.g. sysext/about/mod => typo3/mod/help/about'
+
+                       // If an XCLASS was requested for autoloading, the autoloader has to know which class will be extended
+                       // only with this information it is possible to get the "relative path" of the extended class.
+                       // The "relative path" is needed to simulate the correct path for $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'][$relativePath]
+                       // "relative path" is in quotes, because this is not every time the case. Often we have special cases like EXT:about
+                       // The old way to include an XCLASS is defined by such a piece of code at the end of a class:
+                       //
+                       // if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_beuserauth.php'])) {
+                       //              include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_beuserauth.php']);
+                       // }
+                       //
+                       // This kind of XCLASS inclusion is deprecated now and can be deleted in TYPO3 6.2.
+                       // The whole XCLASS magic is AFTER the autoloading magic from Extbase, because Extbase classes are extended
+                       // by another way
+
+                       // If a class could not be found in the autoloader cache AND an XCLASS is requested by the autoloader
+                       // remove the first XCLASS prefix ("ux_") to get the class which will be extended.
+                       // With this way we support recursive XCLASSing ("ux_ux_ux_...").
+                       // e.g. ux_t3lib_beuserauth => t3lib_beuserauth
+               $baseClassOfXClass = NULL;
+               $xClassRequested = FALSE;
+               if ($classPath === NULL && substr($classNameLower, 0, 3) === 'ux_') {
+                       $baseClassOfXClass = substr($classNameLower, 3);
+                       $xClassRequested = TRUE;
+               }
+
+                       // At this point we know the class which will be extended by the XCLASS and
+                       // try to find the base class in the autoloader again.
+                       // This has to be the second try, because at the first time (a few lines before)
+                       // try to find the XCLASS in the autoloader cache
+               if ($classPath === NULL && array_key_exists($baseClassOfXClass, self::$classNameToFileMapping)) {
+                       $classPath = self::$classNameToFileMapping[$baseClassOfXClass];
+               }
+
+                       // If we got a physical path of the base class which will be extended by the requested XCLASS
+                       // AND an XCLASS is requested by the autoloader
+                       // AND in this TYPO3 installation make use of XCLASSes in general
+                       // then we try to determine the "relative path" for the old XCLASS way
+                       // e.g. "t3lib/class.t3lib_beuserauth.php" of
+                       // $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_beuserauth.php']
+               if ($classPath !== NULL && $xClassRequested === TRUE && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'])) {
+
+                               // Check if the XCLASS for the requested path is set  a transformation for some paths needs to be done
+                       $relativeClassPath = substr($classPath, strlen(PATH_site));
+
+                               // @todo the complete replacing list is not complete right now. e.g. typo3/systext/filelist, ...
+
+                               // Replacement for sysext "about"
+                               // @todo Which modules are affected? Which modules was moved from typo3/mod to another location?
+                       $relativeClassPath = str_replace(
+                               array(
+                                       'sysext/about/mod',
+                                       'typo3/sysext/cms/tslib',
+                                       'typo3conf/ext',
+                                       'typo3/sysext',
+                               ),
+                               array(
+                                       'typo3/mod/help/about',
+                                       'tslib',
+                                       'ext',
+                                       'ext',
+                               ),
+                               $relativeClassPath
+                       );
+
+                       if (isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'][$relativeClassPath])) {
+                               $classPath = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'][$relativeClassPath];
+                               self::addClassToCache($classPath, $classNameLower);
+                       } else {
+
+                                       // If an XCLASS is requested (this is the normal case, for every class min. one time,
+                                       // see t3lib_div::getClassName for more information) AND no XLASS was definied
+                                       // $classPath is filled with the path of the class which will be extended.
+                                       // Because withoud the $classPath of base class, we can not determine the "relative path"
+                                       // to find the correct XCLASS.
+                                       //
+                                       // If no XCLASS is defined, we set $classPath to NULL, because otherwise the autoloader will
+                                       // be load the same class twice (the autoloader use "require" instead of "require_once")
+                                       //
+                                       // A small example:
+                                       // Something requested ux_t3lib_l10n_locales. This class will be not find in the autoloader cache.
+                                       // After this, we try to determine the path of base class, in our case t3lib_l10n_locales
+                                       // (to determine the relative class for old XCLASS inclusion).
+                                       // So we determine the relative path ob the base class ('t3lib/l10n/class.t3lib_l10n_locales.php')
+                                       // and have a look up for definied XCLASSes of t3lib_l10n_locales
+                                       // ($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/l10n/class.t3lib_l10n_locales.php'])
+                                       // If we found one XCLASS, we will return the physical path of this class
+                                       // If no XCLASS was found, we MUST set $classPath to NULL
+                                       // Without this step the physical path of t3lib_l10n_locales will be returned and a
+                                       // "Cannot redeclear class t3lib_l10n_locales"-Error will occur
+                               $classPath = NULL;
+                       }
+               }
+
                return $classPath;
        }
 
@@ -192,10 +304,7 @@ class t3lib_autoloader {
                                        // This will throw a BadFunctionCallException if the extension is not loaded
                                $extensionPath = t3lib_extMgm::extPath($extensionKey);
                                $classFilePathAndName = $extensionPath . 'Classes/' . strtr($classNameParts[2], '_', '/') . '.php';
-                               if (file_exists($classFilePathAndName)) {
-                                       self::$classNameToFileMapping[strtolower($className)] = $classFilePathAndName;
-                                       self::updateRegistryCacheEntry(self::$classNameToFileMapping);
-                               }
+                               self::addClassToCache($classFilePathAndName, $className);
                        } catch (BadFunctionCallException $exception) {
                                        // Catch the exception and do nothing to give
                                        // other registered autoloaders a chance to find the file
@@ -203,6 +312,21 @@ class t3lib_autoloader {
                }
        }
 
+       /**
+        * Adds a single class to autoloader cache.
+        *
+        * @static
+        * @param string $classFilePathAndName Physical path of file containing $className
+        * @param string $className Class name
+        * @return void
+        */
+       protected static function addClassToCache($classFilePathAndName, $className) {
+               if (file_exists($classFilePathAndName)) {
+                       self::$classNameToFileMapping[strtolower($className)] = $classFilePathAndName;
+                       self::updateRegistryCacheEntry(self::$classNameToFileMapping);
+               }
+       }
+
        /**
         * Set or update autoloader cache entry
         *
index c478775..a09be08 100644 (file)
@@ -44,9 +44,13 @@ class t3lib_cache {
         */
        public static function initializeCachingFramework() {
                if (!self::isCachingFrameworkInitialized()) {
-                       $GLOBALS['typo3CacheManager'] = t3lib_div::makeInstance('t3lib_cache_Manager');
+                       $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']);
-                       $GLOBALS['typo3CacheFactory'] = t3lib_div::makeInstance('t3lib_cache_Factory', 'production', $GLOBALS['typo3CacheManager']);
+                       $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 31084c8..dcd006c 100644 (file)
@@ -4472,8 +4472,7 @@ final class t3lib_div {
         *              t3lib_div::makeInstance('myClass', $arg1, $arg2, ..., $argN)
         *
         * @throws InvalidArgumentException if classname is an empty string
-        * @param string $className
-        *                      name of the class to instantiate, must not be empty
+        * @param string $className name of the class to instantiate, must not be empty
         * @return object the created instance
         */
        public static function makeInstance($className) {
@@ -4484,7 +4483,7 @@ final class t3lib_div {
                        // 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::$finalClassNameRegister[$className] = self::getClassName($className);
+                       self::addClassNameToMakeInstanceCache($className, self::getClassName($className));
                }
                $finalClassName = self::$finalClassNameRegister[$className];
 
@@ -4528,7 +4527,7 @@ final class t3lib_div {
         */
        protected static function getClassName($className) {
                if (class_exists($className)) {
-                       while (class_exists('ux_' . $className, FALSE)) {
+                       while (t3lib_autoloader::getClassPathByRegistryLookup('ux_' . $className) !== NULL) {
                                $className = 'ux_' . $className;
                        }
                }
@@ -4542,8 +4541,6 @@ final class t3lib_div {
         * 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
@@ -4556,6 +4553,20 @@ final class t3lib_div {
                self::$singletonInstances[$className] = $instance;
        }
 
+       /**
+        * Adds a $className / $finalClassName to the cache register.
+        * This register is used to determine the final class name only once instead of multiple times.
+        *
+        * @static
+        * @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
+        */
+       public static function addClassNameToMakeInstanceCache($className, $finalClassName) {
+               self::$finalClassNameRegister[$className] = $finalClassName;
+       }
+
        /**
         * Sets the instance of a non-singleton class to be returned by makeInstance.
         *
@@ -5393,4 +5404,4 @@ final class t3lib_div {
        }
 }
 
-?>
\ No newline at end of file
+?>
index ac7d07d..5b3e7d0 100644 (file)
@@ -794,13 +794,17 @@ function initializeCachingFramework() {
        t3lib_cache::initializeCachingFramework();
 }
 
+       // The autoloader must be required BEFORE the initialization of the caching framework,
+       // because we need one method from the autoloader in t3lib_div::getClassName
+       // which will be used during the caching framework startup
+require_once(PATH_t3lib . 'class.t3lib_autoloader.php');
+
 initializeCachingFramework();
 
 
 // *********************
 // Autoloader
 // *********************
-require_once(PATH_t3lib . 'class.t3lib_autoloader.php');
 t3lib_autoloader::registerAutoloader();
 
 /**
@@ -1074,6 +1078,19 @@ if ($TYPO3_LOADED_EXT['_CACHEFILE'])     {
        }
 }
 
+       // Here we check if this TYPO3 installation uses XCLASSes via the deprecated way
+       // If is this the case throw deprecation warning, as the ext_autoload.php way is the preferred way now
+       // This deprecation warning must be thrown here, because of two points
+       // 1. Only one warning will be called per page impression. If you throw this warning in
+       //        t3lib_autoloader::getClassPathByRegistryLookup, it will be thrown for EVERY requested class
+       // 2. It is not possible to throw this warning in t3lib_autoloader::getClassPathByRegistryLookup
+       //        because t3lib_div::deprecationLog use t3lib_div::makeInstance again and will be call
+       //        t3lib_autoloader::getClassPathByRegistryLookup again. This will be end in an endlessloop.
+       // So, after loading all ext_localconf.php`s it will be clear if XCLASSes are used by the old way.
+if(isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']) === TRUE) {
+       t3lib_div::deprecationLog('This installation runs with extensions that use XCLASSing by setting the XCLASS path in ext_localconf.php. This backwards compatibility will be removed in TYPO3 6.2 and later. It is preferred to define XCLASSes in ext_autoload.php instead.');
+}
+
        // Extensions may register new caches, so we set the
        // global cache array to the manager again at this point
 $GLOBALS['typo3CacheManager']->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);