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