[BUGFIX] ClassLoader error after installing extension
[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 *
38 * @author Dmitry Dulepov <dmitry@typo3.org>
39 * @author Martin Kutschker <masi@typo3.org>
40 * @author Oliver Hader <oliver@typo3.org>
41 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
42 * @author Christian Kuhn <lolli@schwarzbu.ch>
43 */
44 class ClassLoader {
45
46 /**
47 * Contains the class loaders class name
48 *
49 * @var string
50 */
51 static protected $className = __CLASS__;
52
53 /**
54 * Class name to file mapping. Key: class name. Value: fully qualified file name.
55 *
56 * @var array
57 */
58 static protected $classNameToFileMapping = array();
59
60 /**
61 * @var boolean TRUE, if old to new and new to old mapping was populated to PHP
62 */
63 static protected $mappingLoaded = FALSE;
64
65 /**
66 * Old class name to new class name mapping
67 *
68 * @var array
69 */
70 static protected $aliasToClassNameMapping = array();
71
72 /**
73 * New class name to old class name mapping
74 *
75 * @var array
76 */
77 static protected $classNameToAliasMapping = array();
78
79 /**
80 * Name of cache entry identifier in autoload cache
81 *
82 * @var string
83 */
84 static protected $classLoaderCacheIdentifier = NULL;
85
86 /**
87 * Track if the cache file written to disk should be updated.
88 * This is set to TRUE if during script run new classes are
89 * found (for example due to new requested extbase classes)
90 * and is used in unregisterAutoloader() to decide whether or not
91 * the cache file should be re-written.
92 *
93 * @var bool True if mapping changed
94 */
95 static protected $cacheUpdateRequired = FALSE;
96
97 /**
98 * The class loader is static, thus we do not allow instances of this class.
99 */
100 private function __construct() {
101
102 }
103
104 /**
105 * Installs TYPO3 class loader, and loads the autoload registry for the core.
106 *
107 * @return boolean TRUE in case of success
108 */
109 static public function registerAutoloader() {
110 static::loadClassLoaderCache();
111 return spl_autoload_register(static::$className . '::autoload', TRUE, TRUE);
112 }
113
114 /**
115 * Unload TYPO3 class loader and write any additional classes
116 * found during the script run to the cache file.
117 *
118 * This method is called during shutdown of the framework.
119 *
120 * @return boolean TRUE in case of success
121 */
122 static public function unregisterAutoloader() {
123 if (static::$cacheUpdateRequired) {
124 static::updateClassLoaderCacheEntry(array(static::$classNameToFileMapping, static::$aliasToClassNameMapping));
125 static::$cacheUpdateRequired = FALSE;
126 }
127 static::$classNameToFileMapping = array();
128 static::$aliasToClassNameMapping = array();
129 static::$classNameToAliasMapping = array();
130 return spl_autoload_unregister(static::$className . '::autoload');
131 }
132
133 /**
134 * Autoload function for TYPO3.
135 *
136 * This method looks up class names in the registry
137 * (which contains extensions and core files)
138 *
139 * @param string $className Class name
140 * @return void
141 */
142 static public function autoload($className) {
143 $className = ltrim($className, '\\');
144 $realClassName = static::getClassNameForAlias($className);
145 $aliasClassName = static::getAliasForClassName($className);
146 $hasAliasClassName = ($aliasClassName !== $className);
147 $lookUpClassName = ($hasRealClassName = $className !== $realClassName) ? $realClassName : $className;
148 // Use core and extension registry
149 $classPath = static::getClassPathByRegistryLookup($lookUpClassName);
150 if ($classPath && !class_exists($realClassName, FALSE)) {
151 // Include the required file that holds the class
152 // Handing over the class name here is only done for the
153 // compatibility class loader so that it can skip class names
154 // which do not require rewriting. We can remove this bad
155 // code smell once we can get rid of the compatibility class loader.
156 static::requireClassFileOnce($classPath, $className);
157 try {
158 // Regular expression for a valid classname taken from
159 // http://www.php.net/manual/en/language.oop5.basic.php
160 if (preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*$/', $className)) {
161 spl_autoload($className);
162 }
163 } catch (\LogicException $exception) {
164
165 }
166 }
167 if ($hasRealClassName && !class_exists($className, FALSE)) {
168 class_alias($realClassName, $className);
169 }
170 if ($hasAliasClassName && !class_exists($aliasClassName, FALSE)) {
171 class_alias($className, $aliasClassName);
172 }
173 }
174
175 /**
176 * Require the class file
177 *
178 * @static
179 * @param string $classPath
180 * @param string $className
181 */
182 static protected function requireClassFileOnce($classPath, $className) {
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 public 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 (!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
303 return $classPath;
304 }
305
306 /**
307 * Find all ext_autoload files and merge with core_autoload.
308 *
309 * @return array
310 */
311 static protected function createCoreAndExtensionRegistry() {
312 $classRegistry = array();
313 // At this point during bootstrap the local configuration is initialized,
314 // ExtensionManagementUtility is ready to get the list of enabled extensions
315 foreach (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray() as $extensionKey) {
316 try {
317 $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey, 'ext_autoload.php');
318 if (@file_exists($extensionAutoloadFile)) {
319 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
320 }
321 } catch (\BadFunctionCallException $e) {
322
323 }
324 }
325 return $classRegistry;
326 }
327
328 /**
329 * Try to load a given class name based on 'extbase' naming convention into the registry.
330 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
331 * array to the file system to save this lookup for next call.
332 *
333 * @param string $className Class name to find source file of
334 * @return void
335 */
336 static protected function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
337 $delimiter = '_';
338 $tempClassName = $className;
339 // To handle namespaced class names, get rid of the first backslash
340 // and replace the remaining ones with underscore. This will simulate
341 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
342 if (strpos($className, '\\') !== FALSE) {
343 $tempClassName = ltrim($className, '\\');
344 $delimiter = '\\';
345 }
346 $classNameParts = explode($delimiter, $tempClassName, 4);
347 if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && (isset($classNameParts[1]) && $classNameParts[1] === 'CMS')) {
348 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[2]);
349 $classNameWithoutVendorAndProduct = $classNameParts[3];
350 } else {
351 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[1]);
352 $classNameWithoutVendorAndProduct = $classNameParts[2];
353
354 if (isset($classNameParts[3])) {
355 $classNameWithoutVendorAndProduct .= $delimiter . $classNameParts[3];
356 }
357 }
358
359 if ($extensionKey) {
360 try {
361 // This will throw a BadFunctionCallException if the extension is not loaded
362 $extensionPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey);
363 $classPath = (substr(strtolower($classNameWithoutVendorAndProduct), 0, 5) === 'tests') ? '' : 'Classes/';
364 $classFilePathAndName = $extensionPath . $classPath . strtr($classNameWithoutVendorAndProduct, $delimiter, '/') . '.php';
365 static::addClassToCache($classFilePathAndName, $className);
366 } catch (\BadFunctionCallException $exception) {
367
368 }
369 }
370 }
371
372 /**
373 * Adds a single class to class loader cache.
374 *
375 * @static
376 * @param string $classFilePathAndName Physical path of file containing $className
377 * @param string $className Class name
378 * @return void
379 */
380 static protected function addClassToCache($classFilePathAndName, $className) {
381 if (file_exists($classFilePathAndName)) {
382 static::$cacheUpdateRequired = TRUE;
383 static::$classNameToFileMapping[GeneralUtility::strtolower($className)] = $classFilePathAndName;
384 }
385 }
386
387 /**
388 * Set or update class loader cache entry.
389 * It is expected that all class names (keys) are already lowercased!
390 *
391 * @param array $cacheContent Current class loader cache entries
392 * @return void
393 */
394 static protected function updateClassLoaderCacheEntry(array $cacheContent) {
395 $cachedFileContent = 'return ' . var_export($cacheContent, TRUE) . ';';
396 $GLOBALS['typo3CacheManager']->getCache('cache_core')->set(static::getClassLoaderCacheIdentifier(), $cachedFileContent);
397 }
398
399 /**
400 * Gets the identifier used for caching the registry files.
401 * The identifier depends on the current TYPO3 version and the
402 * installation path of the TYPO3 site (PATH_site).
403 *
404 * In effect, a new registry cache file will be created
405 * when moving to a newer version with possible new core classes
406 * or moving the webroot to another absolute path.
407 *
408 * @return string identifier
409 */
410 static protected function getClassLoaderCacheIdentifier() {
411 if (is_null(static::$classLoaderCacheIdentifier)) {
412 static::$classLoaderCacheIdentifier = 'ClassLoader_' . sha1((TYPO3_version . PATH_site . 'ClassLoader'));
413 }
414 return static::$classLoaderCacheIdentifier;
415 }
416
417 /**
418 * Lowercase all keys of the class registry.
419 *
420 * Use the multi byte safe version of strtolower from t3lib_div,
421 * so array_change_key_case() can not be used
422 *
423 * @param array $registry Given registry entries
424 * @return array with lower cased keys
425 */
426 static protected function lowerCaseClassRegistry($registry) {
427 $lowerCasedClassRegistry = array();
428 foreach ($registry as $className => $classFile) {
429 $lowerCasedClassRegistry[GeneralUtility::strtolower($className)] = $classFile;
430 }
431 return $lowerCasedClassRegistry;
432 }
433
434 }
435
436
437 ?>