5213cc76cd6ab6ad50986a2c963b5cc9b7116dff
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Core / ClassLoader.php
1 <?php
2 namespace TYPO3\CMS\Core\Core;
3 use \TYPO3\CMS\Core\Utility\GeneralUtility;
4
5 /***************************************************************
6 * Copyright notice
7 *
8 * (c) 2008-2011 Dmitry Dulepov <dmitry@typo3.org>
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30
31 /**
32 * This class contains TYPO3 class loader for classes.
33 * It handles:
34 * - The core of TYPO3
35 * - All extensions with an ext_autoload.php file
36 * - All extensions that stick to the 'extbase' like naming convention
37 * - Resolves registered XCLASSes
38 *
39 * @author Dmitry Dulepov <dmitry@typo3.org>
40 * @author Martin Kutschker <masi@typo3.org>
41 * @author Oliver Hader <oliver@typo3.org>
42 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
43 * @author Christian Kuhn <lolli@schwarzbu.ch>
44 */
45 class ClassLoader {
46
47 /**
48 * Contains the class loaders class name
49 *
50 * @var string
51 */
52 static protected $className = __CLASS__;
53
54 /**
55 * Class name to file mapping. Key: class name. Value: fully qualified file name.
56 *
57 * @var array
58 */
59 static protected $classNameToFileMapping = array();
60
61 /**
62 * @var boolean TRUE, if old to new and new to old mapping was populated to PHP
63 */
64 static protected $mappingLoaded = FALSE;
65
66 /**
67 * Old class name to new class name mapping
68 *
69 * @var array
70 */
71 static protected $aliasToClassNameMapping = array();
72
73 /**
74 * New class name to old class name mapping
75 *
76 * @var array
77 */
78 static protected $classNameToAliasMapping = array();
79
80 /**
81 * Name of cache entry identifier in autoload cache
82 *
83 * @var string
84 */
85 static protected $classLoaderCacheIdentifier = NULL;
86
87 /**
88 * Track if the cache file written to disk should be updated.
89 * This is set to TRUE if during script run new classes are
90 * found (for example due to new requested extbase classes)
91 * and is used in unregisterAutoloader() to decide whether or not
92 * the cache file should be re-written.
93 *
94 * @var bool True if mapping changed
95 */
96 static protected $cacheUpdateRequired = FALSE;
97
98 /**
99 * The class loader is static, thus we do not allow instances of this class.
100 */
101 private function __construct() {
102
103 }
104
105 /**
106 * Installs TYPO3 class loader, and loads the autoload registry for the core.
107 *
108 * @return boolean TRUE in case of success
109 */
110 static public function registerAutoloader() {
111 static::loadClassLoaderCache();
112 return spl_autoload_register(static::$className . '::autoload', TRUE, TRUE);
113 }
114
115 /**
116 * Unload TYPO3 class loader and write any additional classes
117 * found during the script run to the cache file.
118 *
119 * This method is called during shutdown of the framework.
120 *
121 * @return boolean TRUE in case of success
122 */
123 static public function unregisterAutoloader() {
124 if (static::$cacheUpdateRequired) {
125 static::updateClassLoaderCacheEntry(array(static::$classNameToFileMapping, static::$aliasToClassNameMapping));
126 static::$cacheUpdateRequired = FALSE;
127 }
128 static::$classNameToFileMapping = array();
129 static::$aliasToClassNameMapping = array();
130 static::$classNameToAliasMapping = array();
131 return spl_autoload_unregister(static::$className . '::autoload');
132 }
133
134 /**
135 * Autoload function for TYPO3.
136 *
137 * This method looks up class names in the registry
138 * (which contains extensions and core files)
139 *
140 * @param string $className Class name
141 * @return void
142 */
143 static public function autoload($className) {
144 $className = ltrim($className, '\\');
145 $realClassName = static::getClassNameForAlias($className);
146 $aliasClassName = static::getAliasForClassName($className);
147 $hasAliasClassName = ($aliasClassName !== $className);
148 $lookUpClassName = ($hasRealClassName = $className !== $realClassName) ? $realClassName : $className;
149 // Use core and extension registry
150 $classPath = static::getClassPathByRegistryLookup($lookUpClassName);
151 if ($classPath && !class_exists($realClassName, FALSE)) {
152 // Include the required file that holds the class
153 // Handing over the class name here is only done for the
154 // compatibility class loader so that it can skip class names
155 // which do not require rewriting. We can remove this bad
156 // code smell once we can get rid of the compatibility class loader.
157 static::requireClassFileOnce($classPath, $className);
158 try {
159 // Regular expression for a valid classname taken from
160 // http://www.php.net/manual/en/language.oop5.basic.php
161 if (preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $className)) {
162 spl_autoload($className);
163 }
164 } catch (\LogicException $exception) {
165
166 }
167 }
168 if ($hasRealClassName && !class_exists($className, FALSE)) {
169 class_alias($realClassName, $className);
170 }
171 if ($hasAliasClassName && !class_exists($aliasClassName, FALSE)) {
172 class_alias($className, $aliasClassName);
173 }
174 }
175
176 /**
177 * Require the class file
178 *
179 * @static
180 * @param string $classPath
181 */
182 static protected function requireClassFileOnce($classPath) {
183 GeneralUtility::requireOnce($classPath);
184 }
185
186 /**
187 * Load registry from cache file if available or search
188 * for all loaded extensions and create a cache file
189 *
190 * @return void
191 */
192 static protected function loadClassLoaderCache() {
193 $classRegistry = NULL;
194 $aliasToClassNameMapping = NULL;
195 /** @var $phpCodeCache \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend */
196 $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_core');
197 // Create autoload cache file if it does not exist yet
198 if ($phpCodeCache->has(static::getClassLoaderCacheIdentifier())) {
199 list($classRegistry, $aliasToClassNameMapping) = $phpCodeCache->requireOnce(static::getClassLoaderCacheIdentifier());
200 }
201 // This can only happen if the class loader was already registered
202 // in the same call once, the requireOnce of the cache file then
203 // does not give the cached array back. In this case we just read
204 // all cache entries manually again.
205 // This can happen in unit tests and if the cache backend was
206 // switched to NullBackend for example to simplify development
207 if (!is_array($aliasToClassNameMapping)) {
208 static::$cacheUpdateRequired = TRUE;
209 $aliasToClassNameMapping = static::createCoreAndExtensionClassAliasMap();
210 }
211 static::$aliasToClassNameMapping = $aliasToClassNameMapping;
212 static::$classNameToAliasMapping = array_flip($aliasToClassNameMapping);
213 self::setAliasesForEarlyInstances();
214
215 if (!is_array($classRegistry)) {
216 static::$cacheUpdateRequired = TRUE;
217 $classRegistry = static::lowerCaseClassRegistry(static::createCoreAndExtensionRegistry());
218 }
219 static::$classNameToFileMapping = $classRegistry;
220 }
221
222 /**
223 * Collects and merges the class alias maps of extensions
224 *
225 * @return array The class alias map
226 */
227 static protected function createCoreAndExtensionClassAliasMap() {
228 $aliasToClassNameMapping = array();
229 foreach (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray() as $extensionKey) {
230 try {
231 $extensionClassAliasMap = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey, 'Migrations/Code/ClassAliasMap.php');
232 if (@file_exists($extensionClassAliasMap)) {
233 $aliasToClassNameMapping = array_merge($aliasToClassNameMapping, require $extensionClassAliasMap);
234 }
235 } catch (\BadFunctionCallException $e) {
236 }
237 }
238 foreach ($aliasToClassNameMapping as $oldClassName => $newClassName) {
239 $aliasToClassNameMapping[GeneralUtility::strtolower($oldClassName)] = $newClassName;
240 }
241 // Order by key length longest first
242 uksort($aliasToClassNameMapping, function($a, $b) {
243 return strlen($b) - strlen($a);
244 });
245 return $aliasToClassNameMapping;
246 }
247
248 /**
249 * Create aliases for early loaded classes
250 */
251 protected static function setAliasesForEarlyInstances() {
252 $classedLoadedPriorToClassLoader = array_intersect(static::$aliasToClassNameMapping, get_declared_classes());
253 if (!empty($classedLoadedPriorToClassLoader)) {
254 foreach ($classedLoadedPriorToClassLoader as $oldClassName => $newClassName) {
255 if (!class_exists($oldClassName, FALSE)) {
256 class_alias($newClassName, $oldClassName);
257 }
258 }
259 }
260 }
261
262 /**
263 * @param string $alias
264 * @return mixed
265 */
266 static public function getClassNameForAlias($alias) {
267 $lookUpClassName = GeneralUtility::strtolower($alias);
268 return isset(static::$aliasToClassNameMapping[$lookUpClassName]) ? static::$aliasToClassNameMapping[$lookUpClassName] : $alias;
269 }
270
271
272 /**
273 * @param string $className
274 * @return mixed
275 */
276 static public function getAliasForClassName($className) {
277 return isset(static::$classNameToAliasMapping[$className]) ? static::$classNameToAliasMapping[$className] : $className;
278 }
279
280 /**
281 * Get the full path to a class by looking it up in the registry.
282 * If not found, returns NULL.
283 *
284 * Warning: This method is public as it is needed by t3lib_div::makeInstance(),
285 * but it is _not_ part of the public API and should not be used in own extensions!
286 *
287 * @param string $className Class name to find source file of
288 * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
289 * @internal
290 */
291 static public function getClassPathByRegistryLookup($className) {
292 $classPath = NULL;
293 $classNameLower = GeneralUtility::strtolower($className);
294 // Try to resolve extbase naming scheme if class is not already in cache file
295 if (substr($classNameLower, 0, 3) !== 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
296 static::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
297 }
298 // Look up class name in cache file
299 if (array_key_exists($classNameLower, static::$classNameToFileMapping)) {
300 $classPath = static::$classNameToFileMapping[$classNameLower];
301 }
302 if ($classPath === NULL && substr($classNameLower, 0, 3) === 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
303 static::$cacheUpdateRequired = TRUE;
304 static::$classNameToFileMapping[$classNameLower] = NULL;
305 }
306 return $classPath;
307 }
308
309 /**
310 * Find all ext_autoload files and merge with core_autoload.
311 *
312 * @return array
313 */
314 static protected function createCoreAndExtensionRegistry() {
315 $classRegistry = array();
316 // At this point during bootstrap the local configuration is initialized,
317 // ExtensionManagementUtility is ready to get the list of enabled extensions
318 foreach (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray() as $extensionKey) {
319 try {
320 $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey, 'ext_autoload.php');
321 if (@file_exists($extensionAutoloadFile)) {
322 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
323 }
324 } catch (\BadFunctionCallException $e) {
325
326 }
327 }
328 return $classRegistry;
329 }
330
331 /**
332 * Try to load a given class name based on 'extbase' naming convention into the registry.
333 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
334 * array to the file system to save this lookup for next call.
335 *
336 * @param string $className Class name to find source file of
337 * @return void
338 */
339 static protected function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
340 $delimiter = '_';
341 $tempClassName = $className;
342 // To handle namespaced class names, get rid of the first backslash
343 // and replace the remaining ones with underscore. This will simulate
344 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
345 if (strpos($className, '\\') !== FALSE) {
346 $tempClassName = ltrim($className, '\\');
347 $delimiter = '\\';
348 }
349 $classNameParts = explode($delimiter, $tempClassName, 4);
350 if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && (isset($classNameParts[1]) && $classNameParts[1] === 'CMS')) {
351 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[2]);
352 $classNameWithoutVendorAndProduct = $classNameParts[3];
353 } else {
354 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[1]);
355 $classNameWithoutVendorAndProduct = $classNameParts[2];
356
357 if (isset($classNameParts[3])) {
358 $classNameWithoutVendorAndProduct .= $delimiter . $classNameParts[3];
359 }
360 }
361
362 if ($extensionKey) {
363 try {
364 // This will throw a BadFunctionCallException if the extension is not loaded
365 $extensionPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey);
366 $classPath = (substr(strtolower($classNameWithoutVendorAndProduct), 0, 5) === 'tests') ? '' : 'Classes/';
367 $classFilePathAndName = $extensionPath . $classPath . strtr($classNameWithoutVendorAndProduct, $delimiter, '/') . '.php';
368 static::addClassToCache($classFilePathAndName, $className);
369 } catch (\BadFunctionCallException $exception) {
370
371 }
372 }
373 }
374
375 /**
376 * Adds a single class to class loader cache.
377 *
378 * @static
379 * @param string $classFilePathAndName Physical path of file containing $className
380 * @param string $className Class name
381 * @return void
382 */
383 static protected function addClassToCache($classFilePathAndName, $className) {
384 if (file_exists($classFilePathAndName)) {
385 static::$cacheUpdateRequired = TRUE;
386 static::$classNameToFileMapping[GeneralUtility::strtolower($className)] = $classFilePathAndName;
387 }
388 }
389
390 /**
391 * Set or update class loader cache entry.
392 * It is expected that all class names (keys) are already lowercased!
393 *
394 * @param array $cacheContent Current class loader cache entries
395 * @return void
396 */
397 static protected function updateClassLoaderCacheEntry(array $cacheContent) {
398 $cachedFileContent = 'return ' . var_export($cacheContent, TRUE) . ';';
399 $GLOBALS['typo3CacheManager']->getCache('cache_core')->set(static::getClassLoaderCacheIdentifier(), $cachedFileContent);
400 }
401
402 /**
403 * Gets the identifier used for caching the registry files.
404 * The identifier depends on the current TYPO3 version and the
405 * installation path of the TYPO3 site (PATH_site).
406 *
407 * In effect, a new registry cache file will be created
408 * when moving to a newer version with possible new core classes
409 * or moving the webroot to another absolute path.
410 *
411 * @return string identifier
412 */
413 static protected function getClassLoaderCacheIdentifier() {
414 if (is_null(static::$classLoaderCacheIdentifier)) {
415 static::$classLoaderCacheIdentifier = 'ClassLoader_' . sha1((TYPO3_version . PATH_site . 'ClassLoader'));
416 }
417 return static::$classLoaderCacheIdentifier;
418 }
419
420 /**
421 * Lowercase all keys of the class registry.
422 *
423 * Use the multi byte safe version of strtolower from t3lib_div,
424 * so array_change_key_case() can not be used
425 *
426 * @param array $registry Given registry entries
427 * @return array with lower cased keys
428 */
429 static protected function lowerCaseClassRegistry($registry) {
430 $lowerCasedClassRegistry = array();
431 foreach ($registry as $className => $classFile) {
432 $lowerCasedClassRegistry[GeneralUtility::strtolower($className)] = $classFile;
433 }
434 return $lowerCasedClassRegistry;
435 }
436
437 }
438
439
440 ?>