[BUGFIX] Fatal errors for interfaces with PHP version < 5.3.7
[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 autoloader 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 $autoloadCacheIdentifier = 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 autoloader is static, thus we do not allow instances of this class.
100 */
101 private function __construct() {
102
103 }
104
105 /**
106 * Installs TYPO3 autoloader, and loads the autoload registry for the core.
107 *
108 * @return boolean TRUE in case of success
109 */
110 static public function registerAutoloader() {
111 if (!static::$mappingLoaded) {
112 static::loadCoreClassAliasMapping();
113 static::$mappingLoaded = TRUE;
114 }
115 static::loadCoreAndExtensionRegistry();
116 return spl_autoload_register(static::$className . '::autoload', TRUE, TRUE);
117 }
118
119 /**
120 * Unload TYPO3 autoloader and write any additional classes
121 * found during the script run to the cache file.
122 *
123 * This method is called during shutdown of the framework.
124 *
125 * @return boolean TRUE in case of success
126 */
127 static public function unregisterAutoloader() {
128 if (static::$cacheUpdateRequired) {
129 static::updateRegistryCacheEntry(static::$classNameToFileMapping);
130 static::$cacheUpdateRequired = FALSE;
131 }
132 static::$classNameToFileMapping = array();
133 return spl_autoload_unregister(static::$className . '::autoload');
134 }
135
136 /**
137 * Autoload function for TYPO3.
138 *
139 * This method looks up class names in the registry
140 * (which contains extensions and core files)
141 *
142 * @param string $className Class name
143 * @return void
144 */
145 static public function autoload($className) {
146 $className = ltrim($className, '\\');
147 $realClassName = static::getClassNameForAlias($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 static::requireClassFileOnce($classPath);
154 try {
155 // Regular expression for a valid classname taken from
156 // http://www.php.net/manual/en/language.oop5.basic.php
157 if (preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $className)) {
158 spl_autoload($className);
159 }
160 } catch (\LogicException $exception) {
161
162 }
163 }
164 if ($hasRealClassName && !class_exists($className, FALSE)) {
165 class_alias($realClassName, $className);
166 }
167 }
168
169 /**
170 * Require the class file
171 *
172 * @static
173 * @param string $classPath
174 */
175 static protected function requireClassFileOnce($classPath) {
176 GeneralUtility::requireOnce($classPath);
177 }
178
179 /**
180 * Load registry from cache file if available or search
181 * for all loaded extensions and create a cache file
182 *
183 * @return void
184 */
185 static protected function loadCoreAndExtensionRegistry() {
186 /** @var $phpCodeCache \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend */
187 $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_core');
188 // Create autoload cache file if it does not exist yet
189 if ($phpCodeCache->has(static::getAutoloadCacheIdentifier())) {
190 $classRegistry = $phpCodeCache->requireOnce(static::getAutoloadCacheIdentifier());
191 } else {
192 static::$cacheUpdateRequired = TRUE;
193 $classRegistry = static::lowerCaseClassRegistry(static::createCoreAndExtensionRegistry());
194 }
195 // This can only happen if the autoloader was already registered
196 // in the same call once, the requireOnce of the cache file then
197 // does not give the cached array back. In this case we just read
198 // all cache entries manually again.
199 // This can happen in unit tests and if the cache backend was
200 // switched to NullBackend for example to simplify development
201 if (!is_array($classRegistry)) {
202 static::$cacheUpdateRequired = TRUE;
203 $classRegistry = static::lowerCaseClassRegistry(static::createCoreAndExtensionRegistry());
204 }
205 static::$classNameToFileMapping = $classRegistry;
206 }
207
208 static public function loadCoreClassAliasMapping() {
209 static::$aliasToClassNameMapping = require __DIR__ . '/../../Migrations/Code/ClassAliasMap201208221700.php';
210 // Create aliases for early loaded classes
211 $classedLoadedPriorToAutoloader = array_intersect(static::$aliasToClassNameMapping, get_declared_classes());
212 if (!empty($classedLoadedPriorToAutoloader)) {
213 foreach ($classedLoadedPriorToAutoloader as $oldClassName => $newClassName) {
214 class_alias($newClassName, $oldClassName);
215 }
216 }
217 foreach (static::$aliasToClassNameMapping as $oldClassName => $newClassName) {
218 static::$aliasToClassNameMapping[strtolower($oldClassName)] = $newClassName;
219 }
220 static::$classNameToAliasMapping = array_flip(static::$aliasToClassNameMapping);
221 }
222
223 static public function getClassNameForAlias($className) {
224 $lookUpClassName = strtolower($className);
225 return isset(static::$aliasToClassNameMapping[$lookUpClassName]) ? static::$aliasToClassNameMapping[$lookUpClassName] : $className;
226 }
227
228 static public function getAliasForClassName($alias) {
229 return isset(static::$classNameToAliasMapping[$alias]) ? static::$classNameToAliasMapping[$alias] : $alias;
230 }
231
232 /**
233 * Get the full path to a class by looking it up in the registry.
234 * If not found, returns NULL.
235 *
236 * Warning: This method is public as it is needed by t3lib_div::makeInstance(),
237 * but it is _not_ part of the public API and should not be used in own extensions!
238 *
239 * @param string $className Class name to find source file of
240 * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
241 * @internal
242 */
243 static public function getClassPathByRegistryLookup($className) {
244 $classPath = NULL;
245 $classNameLower = GeneralUtility::strtolower($className);
246 // Try to resolve extbase naming scheme if class is not already in cache file
247 if (substr($classNameLower, 0, 3) !== 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
248 static::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
249 }
250 // Look up class name in cache file
251 if (array_key_exists($classNameLower, static::$classNameToFileMapping)) {
252 $classPath = static::$classNameToFileMapping[$classNameLower];
253 }
254 if ($classPath === NULL && substr($classNameLower, 0, 3) === 'ux_' && !array_key_exists($classNameLower, static::$classNameToFileMapping)) {
255 static::$cacheUpdateRequired = TRUE;
256 static::$classNameToFileMapping[$classNameLower] = NULL;
257 }
258 return $classPath;
259 }
260
261 /**
262 * Find all ext_autoload files and merge with core_autoload.
263 *
264 * @return array
265 */
266 static protected function createCoreAndExtensionRegistry() {
267 $classRegistry = require(PATH_t3lib . 'core_autoload.php');
268 // At this point during bootstrap the local configuration is initialized,
269 // extMgm is ready to get the list of enabled extensions
270 foreach (\TYPO3\CMS\Core\Extension\ExtensionManager::getLoadedExtensionListArray() as $extensionKey) {
271 try {
272 $extensionAutoloadFile = \TYPO3\CMS\Core\Extension\ExtensionManager::extPath($extensionKey, 'ext_autoload.php');
273 if (@file_exists($extensionAutoloadFile)) {
274 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
275 }
276 } catch (\BadFunctionCallException $e) {
277
278 }
279 }
280 return $classRegistry;
281 }
282
283 /**
284 * Try to load a given class name based on 'extbase' naming convention into the registry.
285 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
286 * array to the file system to save this lookup for next call.
287 *
288 * @param string $className Class name to find source file of
289 * @return void
290 */
291 static protected function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
292 $delimiter = '_';
293 $tempClassName = $className;
294 // To handle namespaced class names, get rid of the first backslash
295 // and replace the remaining ones with underscore. This will simulate
296 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
297 if (strpos($className, '\\') !== FALSE) {
298 $tempClassName = ltrim($className, '\\');
299 $delimiter = '\\';
300 }
301 $classNameParts = explode($delimiter, $tempClassName, 4);
302 if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && (isset($classNameParts[1]) && $classNameParts[1] === 'CMS')) {
303 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[2]);
304 $classNameWithoutVendorAndProduct = $classNameParts[3];
305 } else {
306 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[1]);
307 $classNameWithoutVendorAndProduct = $classNameParts[2];
308
309 if (isset($classNameParts[3])) {
310 $classNameWithoutVendorAndProduct .= $delimiter . $classNameParts[3];
311 }
312 }
313
314 if ($extensionKey) {
315 try {
316 // This will throw a BadFunctionCallException if the extension is not loaded
317 $extensionPath = \TYPO3\CMS\Core\Extension\ExtensionManager::extPath($extensionKey);
318 $classPath = (substr(strtolower($classNameWithoutVendorAndProduct), 0, 5) === 'tests') ? '' : 'Classes/';
319 $classFilePathAndName = $extensionPath . $classPath . strtr($classNameWithoutVendorAndProduct, $delimiter, '/') . '.php';
320 static::addClassToCache($classFilePathAndName, $className);
321 } catch (\BadFunctionCallException $exception) {
322
323 }
324 }
325 }
326
327 /**
328 * Adds a single class to autoloader cache.
329 *
330 * @static
331 * @param string $classFilePathAndName Physical path of file containing $className
332 * @param string $className Class name
333 * @return void
334 */
335 static protected function addClassToCache($classFilePathAndName, $className) {
336 if (file_exists($classFilePathAndName)) {
337 static::$cacheUpdateRequired = TRUE;
338 static::$classNameToFileMapping[GeneralUtility::strtolower($className)] = $classFilePathAndName;
339 }
340 }
341
342 /**
343 * Set or update autoloader cache entry.
344 * It is expected that all class names (keys) are already lowercased!
345 *
346 * @param array $registry Current registry entries
347 * @return void
348 */
349 static protected function updateRegistryCacheEntry(array $registry) {
350 $cachedFileContent = 'return array(';
351 foreach ($registry as $className => $classLocation) {
352 $nullOrLocation = is_string($classLocation) ? '\'' . $classLocation . '\',' : 'NULL,';
353 $cachedFileContent .= LF . '\'' . $className . '\' => ' . $nullOrLocation;
354 }
355 $cachedFileContent .= LF . ');';
356 $GLOBALS['typo3CacheManager']->getCache('cache_core')->set(static::getAutoloadCacheIdentifier(), $cachedFileContent);
357 }
358
359 /**
360 * Gets the identifier used for caching the registry files.
361 * The identifier depends on the current TYPO3 version and the
362 * installation path of the TYPO3 site (PATH_site).
363 *
364 * In effect, a new registry cache file will be created
365 * when moving to a newer version with possible new core classes
366 * or moving the webroot to another absolute path.
367 *
368 * @return string identifier
369 */
370 static protected function getAutoloadCacheIdentifier() {
371 if (is_null(static::$autoloadCacheIdentifier)) {
372 static::$autoloadCacheIdentifier = 'autoload_' . sha1((TYPO3_version . PATH_site . 'autoload'));
373 }
374 return static::$autoloadCacheIdentifier;
375 }
376
377 /**
378 * Lowercase all keys of the class registry.
379 *
380 * Use the multi byte safe version of strtolower from t3lib_div,
381 * so array_change_key_case() can not be used
382 *
383 * @param array $registry Given registry entries
384 * @return array with lower cased keys
385 */
386 static protected function lowerCaseClassRegistry($registry) {
387 $lowerCasedClassRegistry = array();
388 foreach ($registry as $className => $classFile) {
389 $lowerCasedClassRegistry[GeneralUtility::strtolower($className)] = $classFile;
390 }
391 return $lowerCasedClassRegistry;
392 }
393
394 }
395
396
397 ?>