[FEATURE] Add wincache as a cache backend
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_autoloader.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2011 Dmitry Dulepov <dmitry@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * This class contains TYPO3 autoloader for classes.
30 * It handles:
31 * - The core of TYPO3
32 * - All extensions with an ext_autoload.php file
33 * - All extensions that stick to the 'extbase' like naming convention
34 * - Resolves registered XCLASSes
35 *
36 * @author Dmitry Dulepov <dmitry@typo3.org>
37 * @author Martin Kutschker <masi@typo3.org>
38 * @author Oliver Hader <oliver@typo3.org>
39 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
40 * @author Christian Kuhn <lolli@schwarzbu.ch>
41 */
42 class t3lib_autoloader {
43
44 /**
45 * Class name to file mapping. Key: class name. Value: fully qualified file name.
46 *
47 * @var array
48 */
49 protected static $classNameToFileMapping = array();
50
51 /**
52 * Name of cache entry identifier in autoload cache
53 *
54 * @var string
55 */
56 protected static $autoloadCacheIdentifier = NULL;
57
58 /**
59 * Track if the cache file written to disk should be updated.
60 * This is set to TRUE if during script run new classes are
61 * found (for example due to new requested extbase classes)
62 * and is used in unregisterAutoloader() to decide whether or not
63 * the cache file should be re-written.
64 *
65 * @var bool True if mapping changed
66 */
67 protected static $cacheUpdateRequired = FALSE;
68
69 /**
70 * The autoloader is static, thus we do not allow instances of this class.
71 */
72 private function __construct() {
73 }
74
75 /**
76 * Installs TYPO3 autoloader, and loads the autoload registry for the core.
77 *
78 * @return boolean TRUE in case of success
79 */
80 public static function registerAutoloader() {
81 self::loadCoreAndExtensionRegistry();
82 return spl_autoload_register('t3lib_autoloader::autoload', TRUE, TRUE);
83 }
84
85 /**
86 * Unload TYPO3 autoloader and write any additional classes
87 * found during the script run to the cache file.
88 *
89 * This method is called during shutdown of the framework.
90 *
91 * @return boolean TRUE in case of success
92 */
93 public static function unregisterAutoloader() {
94 if (self::$cacheUpdateRequired) {
95 self::updateRegistryCacheEntry(self::$classNameToFileMapping);
96 self::$cacheUpdateRequired = FALSE;
97 }
98 self::$classNameToFileMapping = array();
99
100 return spl_autoload_unregister('t3lib_autoloader::autoload');
101 }
102
103 /**
104 * Autoload function for TYPO3.
105 *
106 * This method looks up class names in the registry
107 * (which contains extensions and core files)
108 *
109 * @param string $className Class name
110 * @return void
111 */
112 public static function autoload($className) {
113 // Use core and extension registry
114 $classPath = self::getClassPathByRegistryLookup($className);
115
116 if ($classPath) {
117 // Include the required file that holds the class
118 t3lib_div::requireFile($classPath);
119 } else {
120 try {
121 // Regular expression for a valid classname taken from
122 // http://www.php.net/manual/en/language.oop5.basic.php
123 if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $className)) {
124 spl_autoload($className);
125 }
126 } catch (LogicException $exception) {
127 }
128 }
129 }
130
131 /**
132 * Load registry from cache file if available or search
133 * for all loaded extensions and create a cache file
134 *
135 * @return void
136 */
137 protected static function loadCoreAndExtensionRegistry() {
138 /** @var $phpCodeCache t3lib_cache_frontend_PhpFrontend */
139 $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_phpcode');
140
141 // Create autoload cache file if it does not exist yet
142 if ($phpCodeCache->has(self::getAutoloadCacheIdentifier())) {
143 $classRegistry = $phpCodeCache->requireOnce(self::getAutoloadCacheIdentifier());
144 } else {
145 self::$cacheUpdateRequired = TRUE;
146 $classRegistry = self::lowerCaseClassRegistry(self::createCoreAndExtensionRegistry());
147 }
148
149 // This can only happen if the autoloader was already registered
150 // in the same call once, the requireOnce of the cache file then
151 // does not give the cached array back. In this case we just read
152 // all cache entries manually again.
153 // This can happen in unit tests and if the cache backend was
154 // switched to NullBackend for example to simplify development
155 if (!is_array($classRegistry)) {
156 self::$cacheUpdateRequired = TRUE;
157 $classRegistry = self::lowerCaseClassRegistry(self::createCoreAndExtensionRegistry());
158 }
159
160 self::$classNameToFileMapping = $classRegistry;
161 }
162
163 /**
164 * Get the full path to a class by looking it up in the registry.
165 * If not found, returns NULL.
166 *
167 * Warning: This method is public as it is needed by t3lib_div::makeInstance(),
168 * but it is _not_ part of the public API and should not be used in own extensions!
169 *
170 * @param string $className Class name to find source file of
171 * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
172 * @internal
173 */
174 public static function getClassPathByRegistryLookup($className) {
175 $classPath = NULL;
176
177 $classNameLower = t3lib_div::strtolower($className);
178
179 // Try to resolve extbase naming scheme if class is not already in cache file
180 if (!array_key_exists($classNameLower, self::$classNameToFileMapping)) {
181 self::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
182 }
183
184 // Look up class name in cache file
185 if (array_key_exists($classNameLower, self::$classNameToFileMapping)) {
186 $classPath = self::$classNameToFileMapping[$classNameLower];
187 }
188
189 if (
190 $classPath === NULL
191 && substr($classNameLower, 0, 3) === 'ux_'
192 && !array_key_exists($classNameLower, self::$classNameToFileMapping)
193 ) {
194 self::$cacheUpdateRequired = TRUE;
195 self::$classNameToFileMapping[$classNameLower] = NULL;
196 }
197
198 return $classPath;
199 }
200
201 /**
202 * Find all ext_autoload files and merge with core_autoload.
203 *
204 * @return array
205 */
206 protected static function createCoreAndExtensionRegistry() {
207 $classRegistry = require(PATH_t3lib . 'core_autoload.php');
208 // At this point during bootstrap the local configuration is initialized,
209 // extMgm is ready to get the list of enabled extensions
210 foreach (t3lib_extMgm::getLoadedExtensionListArray() as $extensionKey) {
211 $extensionAutoloadFile = t3lib_extMgm::extPath($extensionKey, 'ext_autoload.php');
212 if (file_exists($extensionAutoloadFile)) {
213 $classRegistry = array_merge($classRegistry, require($extensionAutoloadFile));
214 }
215 }
216 return $classRegistry;
217 }
218
219 /**
220 * Try to load a given class name based on 'extbase' naming convention into the registry.
221 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
222 * array to the file system to save this lookup for next call.
223 *
224 * @param string $className Class name to find source file of
225 * @return void
226 */
227 protected static function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
228
229 $delimiter = '_';
230 $tempClassName = $className;
231
232 // To handle namespaced class names, get rid of the first backslash
233 // and replace the remaining ones with underscore. This will simulate
234 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
235 if(strpos($className, '\\') !== FALSE) {
236 $tempClassName = ltrim($className, '\\');
237 $delimiter = '\\';
238 }
239 $classNameParts = explode($delimiter, $tempClassName, 3);
240 $extensionKey = t3lib_div::camelCaseToLowerCaseUnderscored($classNameParts[1]);
241 if ($extensionKey) {
242 try {
243 // This will throw a BadFunctionCallException if the extension is not loaded
244 $extensionPath = t3lib_extMgm::extPath($extensionKey);
245 $classFilePathAndName = $extensionPath . 'Classes/' . strtr($classNameParts[2], $delimiter, '/') . '.php';
246 self::addClassToCache($classFilePathAndName, $className);
247 } catch (BadFunctionCallException $exception) {
248 // Catch the exception and do nothing to give
249 // other registered autoloaders a chance to find the file
250 }
251 }
252 }
253
254 /**
255 * Adds a single class to autoloader cache.
256 *
257 * @static
258 * @param string $classFilePathAndName Physical path of file containing $className
259 * @param string $className Class name
260 * @return void
261 */
262 protected static function addClassToCache($classFilePathAndName, $className) {
263 if (file_exists($classFilePathAndName)) {
264 self::$cacheUpdateRequired = TRUE;
265 self::$classNameToFileMapping[t3lib_div::strtolower($className)] = $classFilePathAndName;
266 }
267 }
268
269 /**
270 * Set or update autoloader cache entry.
271 * It is expected that all class names (keys) are already lowercased!
272 *
273 * @param array $registry Current registry entries
274 * @return void
275 */
276 protected static function updateRegistryCacheEntry(array $registry) {
277 $cachedFileContent = 'return array(';
278 foreach ($registry as $className => $classLocation) {
279 $nullOrLocation = is_string($classLocation) ? '\'' . $classLocation . '\',' : 'NULL,';
280 $cachedFileContent .= LF . '\'' . $className . '\' => ' . $nullOrLocation;
281 }
282 $cachedFileContent .= LF . ');';
283 $GLOBALS['typo3CacheManager']->getCache('cache_phpcode')->set(
284 self::getAutoloadCacheIdentifier(),
285 $cachedFileContent,
286 array('t3lib_autoloader', 'core')
287 );
288 }
289
290 /**
291 * Gets the identifier used for caching the registry files.
292 * The identifier depends on the current TYPO3 version and the
293 * installation path of the TYPO3 site (PATH_site).
294 *
295 * In effect, a new registry cache file will be created
296 * when moving to a newer version with possible new core classes
297 * or moving the webroot to another absolute path.
298 *
299 * @return string identifier
300 */
301 protected static function getAutoloadCacheIdentifier() {
302 if (is_null(self::$autoloadCacheIdentifier)) {
303 self::$autoloadCacheIdentifier = 'autoload_' . sha1(TYPO3_version . PATH_site . 'autoload');
304 }
305 return self::$autoloadCacheIdentifier;
306 }
307
308 /**
309 * Lowercase all keys of the class registry.
310 *
311 * Use the multi byte safe version of strtolower from t3lib_div,
312 * so array_change_key_case() can not be used
313 *
314 * @param array $registry Given registry entries
315 * @return array with lower cased keys
316 */
317 protected static function lowerCaseClassRegistry($registry) {
318 $lowerCasedClassRegistry = array();
319 foreach ($registry as $className => $classFile) {
320 $lowerCasedClassRegistry[t3lib_div::strtolower($className)] = $classFile;
321 }
322 return $lowerCasedClassRegistry;
323 }
324 }
325 ?>