[BUGFIX] Fix early calls to makeInstance
[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 */
181 static protected function requireClassFileOnce($classPath) {
182 GeneralUtility::requireOnce($classPath);
183 }
184
185 /**
186 * Load registry from cache file if available or search
187 * for all loaded extensions and create a cache file
188 *
189 * @return void
190 */
191 static protected function loadClassLoaderCache() {
192 $classRegistry = NULL;
193 $aliasToClassNameMapping = NULL;
194 /** @var $phpCodeCache \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend */
195 $phpCodeCache = $GLOBALS['typo3CacheManager']->getCache('cache_core');
196 // Create autoload cache file if it does not exist yet
197 if ($phpCodeCache->has(static::getClassLoaderCacheIdentifier())) {
198 list($classRegistry, $aliasToClassNameMapping) = $phpCodeCache->requireOnce(static::getClassLoaderCacheIdentifier());
199 }
200 // This can only happen if the class loader was already registered
201 // in the same call once, the requireOnce of the cache file then
202 // does not give the cached array back. In this case we just read
203 // all cache entries manually again.
204 // This can happen in unit tests and if the cache backend was
205 // switched to NullBackend for example to simplify development
206 if (!is_array($aliasToClassNameMapping)) {
207 static::$cacheUpdateRequired = TRUE;
208 $aliasToClassNameMapping = static::createCoreAndExtensionClassAliasMap();
209 }
210 static::$aliasToClassNameMapping = $aliasToClassNameMapping;
211 static::$classNameToAliasMapping = array_flip($aliasToClassNameMapping);
212 self::setAliasesForEarlyInstances();
213
214 if (!is_array($classRegistry)) {
215 static::$cacheUpdateRequired = TRUE;
216 $classRegistry = static::lowerCaseClassRegistry(static::createCoreAndExtensionRegistry());
217 }
218 static::$classNameToFileMapping = $classRegistry;
219 }
220
221 /**
222 * Collects and merges the class alias maps of extensions
223 *
224 * @return array The class alias map
225 */
226 static protected function createCoreAndExtensionClassAliasMap() {
227 $aliasToClassNameMapping = array();
228 foreach (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray() as $extensionKey) {
229 try {
230 $extensionClassAliasMap = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey, 'Migrations/Code/ClassAliasMap.php');
231 if (@file_exists($extensionClassAliasMap)) {
232 $aliasToClassNameMapping = array_merge($aliasToClassNameMapping, require $extensionClassAliasMap);
233 }
234 } catch (\BadFunctionCallException $e) {
235 }
236 }
237 foreach ($aliasToClassNameMapping as $oldClassName => $newClassName) {
238 $aliasToClassNameMapping[GeneralUtility::strtolower($oldClassName)] = $newClassName;
239 }
240 // Order by key length longest first
241 uksort($aliasToClassNameMapping, function($a, $b) {
242 return strlen($b) - strlen($a);
243 });
244 return $aliasToClassNameMapping;
245 }
246
247 /**
248 * Create aliases for early loaded classes
249 */
250 protected static function setAliasesForEarlyInstances() {
251 $classedLoadedPriorToClassLoader = array_intersect(static::$aliasToClassNameMapping, get_declared_classes());
252 if (!empty($classedLoadedPriorToClassLoader)) {
253 foreach ($classedLoadedPriorToClassLoader as $oldClassName => $newClassName) {
254 if (!class_exists($oldClassName, FALSE)) {
255 class_alias($newClassName, $oldClassName);
256 }
257 }
258 }
259 }
260
261 /**
262 * @param string $alias
263 * @return mixed
264 */
265 static public function getClassNameForAlias($alias) {
266 $lookUpClassName = GeneralUtility::strtolower($alias);
267 return isset(static::$aliasToClassNameMapping[$lookUpClassName]) ? static::$aliasToClassNameMapping[$lookUpClassName] : $alias;
268 }
269
270
271 /**
272 * @param string $className
273 * @return mixed
274 */
275 static public function getAliasForClassName($className) {
276 return isset(static::$classNameToAliasMapping[$className]) ? static::$classNameToAliasMapping[$className] : $className;
277 }
278
279 /**
280 * Get the full path to a class by looking it up in the registry.
281 * If not found, returns NULL.
282 *
283 * Warning: This method is public as it is needed by t3lib_div::makeInstance(),
284 * but it is _not_ part of the public API and should not be used in own extensions!
285 *
286 * @param string $className Class name to find source file of
287 * @return mixed If String: Full name of the file where $className is declared, NULL if no entry is found
288 * @internal
289 */
290 static public function getClassPathByRegistryLookup($className) {
291 $classPath = NULL;
292 $classNameLower = GeneralUtility::strtolower($className);
293 // Try to resolve extbase naming scheme if class is not already in cache file
294 if (!array_key_exists($classNameLower, static::$classNameToFileMapping)) {
295 static::attemptToLoadRegistryWithNamingConventionForGivenClassName($className);
296 }
297 // Look up class name in cache file
298 if (array_key_exists($classNameLower, static::$classNameToFileMapping)) {
299 $classPath = static::$classNameToFileMapping[$classNameLower];
300 }
301
302 return $classPath;
303 }
304
305 /**
306 * Find all ext_autoload files and merge with core_autoload.
307 *
308 * @return array
309 */
310 static protected function createCoreAndExtensionRegistry() {
311 $classRegistry = array();
312 // At this point during bootstrap the local configuration is initialized,
313 // ExtensionManagementUtility is ready to get the list of enabled extensions
314 foreach (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getLoadedExtensionListArray() as $extensionKey) {
315 try {
316 $extensionAutoloadFile = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey, 'ext_autoload.php');
317 if (@file_exists($extensionAutoloadFile)) {
318 $classRegistry = array_merge($classRegistry, require $extensionAutoloadFile);
319 }
320 } catch (\BadFunctionCallException $e) {
321
322 }
323 }
324 return $classRegistry;
325 }
326
327 /**
328 * Try to load a given class name based on 'extbase' naming convention into the registry.
329 * If the file is found it writes an entry to $classNameToFileMapping and re-caches the
330 * array to the file system to save this lookup for next call.
331 *
332 * @param string $className Class name to find source file of
333 * @return void
334 */
335 static protected function attemptToLoadRegistryWithNamingConventionForGivenClassName($className) {
336 $delimiter = '_';
337 $tempClassName = $className;
338 // To handle namespaced class names, get rid of the first backslash
339 // and replace the remaining ones with underscore. This will simulate
340 // a 'usual' "extbase" structure like 'Tx_ExtensionName_Foo_bar'
341 if (strpos($className, '\\') !== FALSE) {
342 $tempClassName = ltrim($className, '\\');
343 $delimiter = '\\';
344 }
345 $classNameParts = explode($delimiter, $tempClassName, 4);
346 if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && (isset($classNameParts[1]) && $classNameParts[1] === 'CMS')) {
347 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[2]);
348 $classNameWithoutVendorAndProduct = $classNameParts[3];
349 } else {
350 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[1]);
351 $classNameWithoutVendorAndProduct = $classNameParts[2];
352
353 if (isset($classNameParts[3])) {
354 $classNameWithoutVendorAndProduct .= $delimiter . $classNameParts[3];
355 }
356 }
357
358 if ($extensionKey) {
359 try {
360 // This will throw a BadFunctionCallException if the extension is not loaded
361 $extensionPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($extensionKey);
362 $classPath = (substr(strtolower($classNameWithoutVendorAndProduct), 0, 5) === 'tests') ? '' : 'Classes/';
363 $classFilePathAndName = $extensionPath . $classPath . strtr($classNameWithoutVendorAndProduct, $delimiter, '/') . '.php';
364 static::addClassToCache($classFilePathAndName, $className);
365 } catch (\BadFunctionCallException $exception) {
366
367 }
368 }
369 }
370
371 /**
372 * Adds a single class to class loader cache.
373 *
374 * @static
375 * @param string $classFilePathAndName Physical path of file containing $className
376 * @param string $className Class name
377 * @return void
378 */
379 static protected function addClassToCache($classFilePathAndName, $className) {
380 if (file_exists($classFilePathAndName)) {
381 static::$cacheUpdateRequired = TRUE;
382 static::$classNameToFileMapping[GeneralUtility::strtolower($className)] = $classFilePathAndName;
383 }
384 }
385
386 /**
387 * Set or update class loader cache entry.
388 * It is expected that all class names (keys) are already lowercased!
389 *
390 * @param array $cacheContent Current class loader cache entries
391 * @return void
392 */
393 static protected function updateClassLoaderCacheEntry(array $cacheContent) {
394 $cachedFileContent = 'return ' . var_export($cacheContent, TRUE) . ';';
395 $GLOBALS['typo3CacheManager']->getCache('cache_core')->set(static::getClassLoaderCacheIdentifier(), $cachedFileContent);
396 }
397
398 /**
399 * Gets the identifier used for caching the registry files.
400 * The identifier depends on the current TYPO3 version and the
401 * installation path of the TYPO3 site (PATH_site).
402 *
403 * In effect, a new registry cache file will be created
404 * when moving to a newer version with possible new core classes
405 * or moving the webroot to another absolute path.
406 *
407 * @return string identifier
408 */
409 static protected function getClassLoaderCacheIdentifier() {
410 if (is_null(static::$classLoaderCacheIdentifier)) {
411 static::$classLoaderCacheIdentifier = 'ClassLoader_' . sha1((TYPO3_version . PATH_site . 'ClassLoader'));
412 }
413 return static::$classLoaderCacheIdentifier;
414 }
415
416 /**
417 * Lowercase all keys of the class registry.
418 *
419 * Use the multi byte safe version of strtolower from t3lib_div,
420 * so array_change_key_case() can not be used
421 *
422 * @param array $registry Given registry entries
423 * @return array with lower cased keys
424 */
425 static protected function lowerCaseClassRegistry($registry) {
426 $lowerCasedClassRegistry = array();
427 foreach ($registry as $className => $classFile) {
428 $lowerCasedClassRegistry[GeneralUtility::strtolower($className)] = $classFile;
429 }
430 return $lowerCasedClassRegistry;
431 }
432
433 }
434
435
436 ?>