Revert "[CLEANUP] Move "finalClassNameCache" from GeneralUtility to ClassLoader"
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Core / ClassLoader.php
1 <?php
2 namespace TYPO3\CMS\Core\Core;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Locking\Locker;
18 use TYPO3\CMS\Core\Package\PackageInterface;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Cache;
21
22 /**
23 * Class Loader implementation which loads .php files found in the classes
24 * directory of an object.
25 */
26 class ClassLoader {
27
28 const VALID_CLASSNAME_PATTERN = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9\\\\_\x7f-\xff]*$/';
29
30 /**
31 * @var ApplicationContext
32 */
33 protected $context;
34
35 /**
36 * @var ClassAliasMap
37 */
38 protected $classAliasMap;
39
40 /**
41 * @var ClassAliasMap
42 */
43 static protected $staticAliasMap;
44
45 /**
46 * @var \TYPO3\CMS\Core\Cache\Frontend\StringFrontend
47 */
48 protected $classesCache;
49
50 /**
51 * @var \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
52 */
53 protected $coreCache;
54
55 /**
56 * @var string
57 */
58 protected $cacheIdentifier;
59
60 /**
61 * @var \TYPO3\Flow\Package\Package[]
62 */
63 protected $packages = array();
64
65 /**
66 * @var bool
67 */
68 protected $isEarlyCache = TRUE;
69
70 /**
71 * @var array
72 */
73 protected $runtimeClassLoadingInformationCache = array();
74
75 /**
76 * A list of namespaces this class loader is definitely responsible for
77 *
78 * @var array
79 */
80 protected $packageNamespaces = array();
81
82 /**
83 * A list of packages and their replaces pointing to class paths
84 *
85 * @var array
86 */
87 protected $packageClassesPaths = array();
88
89 /**
90 * Is TRUE while loading the Locker class to prevent a deadlock in the implicit call to loadClass
91 *
92 * @var bool
93 */
94 protected $isLoadingLocker = FALSE;
95
96 /**
97 * @var \TYPO3\CMS\Core\Locking\Locker
98 */
99 protected $lockObject = NULL;
100
101 /**
102 * Is set to TRUE if the shutdown function for die in lock is registered, so it won't be registered twice.
103 *
104 * @var bool
105 */
106 protected $shutdownRegistered = FALSE;
107
108 /**
109 * Constructor
110 *
111 * @param ApplicationContext $context
112 */
113 public function __construct(ApplicationContext $context) {
114 $this->context = $context;
115 $this->classesCache = new Cache\Frontend\StringFrontend('cache_classes', new Cache\Backend\TransientMemoryBackend($context));
116 }
117
118 /**
119 * Get class alias map list injected
120 *
121 * @param ClassAliasMap
122 * @return void
123 */
124 public function injectClassAliasMap(ClassAliasMap $classAliasMap) {
125 $this->classAliasMap = $classAliasMap;
126 static::$staticAliasMap = $classAliasMap;
127 }
128
129 /**
130 * Get core cache injected
131 *
132 * @param \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend $coreCache
133 * @return void
134 */
135 public function injectCoreCache(Cache\Frontend\PhpFrontend $coreCache) {
136 $this->coreCache = $coreCache;
137 $this->classAliasMap->injectCoreCache($coreCache);
138 }
139
140 /**
141 * Get classes cache injected
142 *
143 * @param \TYPO3\CMS\Core\Cache\Frontend\StringFrontend $classesCache
144 * @return void
145 */
146 public function injectClassesCache(Cache\Frontend\StringFrontend $classesCache) {
147 $earlyClassesCache = $this->classesCache;
148 $this->classesCache = $classesCache;
149 $this->isEarlyCache = FALSE;
150 $this->classAliasMap->injectClassesCache($classesCache);
151 foreach ($earlyClassesCache->getByTag('early') as $originalClassLoadingInformation) {
152 $classLoadingInformation = explode("\xff", $originalClassLoadingInformation);
153 $cacheEntryIdentifier = strtolower(str_replace('\\', '_', $classLoadingInformation[1]));
154 if (!$this->classesCache->has($cacheEntryIdentifier)) {
155 $this->classesCache->set($cacheEntryIdentifier, $originalClassLoadingInformation);
156 }
157 }
158 }
159
160 /**
161 * Loads php files containing classes or interfaces found in the classes directory of
162 * a package and specifically registered classes.
163 *
164 * Caution: This function may be called "recursively" by the spl_autoloader if a class depends on another classes.
165 *
166 * @param string $className Name of the class/interface to load
167 * @return bool
168 */
169 public function loadClass($className) {
170 if ($className[0] === '\\') {
171 $className = substr($className, 1);
172 }
173
174 if (!$this->isValidClassName($className)) {
175 return FALSE;
176 }
177
178 $cacheEntryIdentifier = strtolower(str_replace('\\', '_', $className));
179 $classLoadingInformation = $this->getClassLoadingInformationFromCache($cacheEntryIdentifier);
180 // Handle a cache miss
181 if ($classLoadingInformation === FALSE) {
182 $classLoadingInformation = $this->buildCachedClassLoadingInformation($cacheEntryIdentifier, $className);
183 }
184
185 // Class loading information structure
186 // array(
187 // 0 => class file path
188 // 1 => original class name
189 // 2 and following => alias class names
190 // )
191 $loadingSuccessful = FALSE;
192 if (!empty($classLoadingInformation)) {
193 // The call to class_exists/interface_exists fixes a rare case when early instances need to be aliased
194 // but PHP fails to recognize the real path of the class. See #55904
195 $loadingSuccessful = class_exists($classLoadingInformation[1], FALSE)
196 || interface_exists($classLoadingInformation[1], FALSE)
197 || (bool)require_once $classLoadingInformation[0];
198 }
199 if ($loadingSuccessful && count($classLoadingInformation) > 2) {
200 $originalClassName = $classLoadingInformation[1];
201 foreach (array_slice($classLoadingInformation, 2) as $aliasClassName) {
202 $this->setAliasForClassName($aliasClassName, $originalClassName);
203 }
204 }
205
206 return $loadingSuccessful;
207 }
208
209 /**
210 * Get class loading information for the given identifier for cache
211 * Return values:
212 * - array with class information (empty if the class is invalid)
213 * - FALSE if no class information is found in cache (cache miss)
214 * - NULL if the cache identifier is invalid (cache failure)
215 *
216 * @param string $cacheEntryIdentifier The identifier to fetch entry from cache
217 * @return array|FALSE The class information, empty array if class is unknown or FALSE if class information was not found in cache.
218 */
219 public function getClassLoadingInformationFromCache($cacheEntryIdentifier) {
220 $rawClassLoadingInformation = $this->classesCache->get($cacheEntryIdentifier);
221
222 if ($rawClassLoadingInformation === '') {
223 return array();
224 }
225
226 if ($rawClassLoadingInformation) {
227 return explode("\xff", $rawClassLoadingInformation);
228 }
229 return FALSE;
230 }
231
232 /**
233 * Builds the class loading information and writes it to the cache. It handles Locking for this cache.
234 *
235 * Caution: The function loadClass can be called "recursively" by spl_autoloader. This needs to be observed when
236 * locking for cache access. Only the first call to loadClass may acquire and release the lock!
237 *
238 * @param string $cacheEntryIdentifier Cache identifier for this class
239 * @param string $className Name of class this information is for
240 *
241 * @return array|FALSE The class information, empty array if class is unknown or FALSE if class information was not found in cache.
242 */
243 protected function buildCachedClassLoadingInformation($cacheEntryIdentifier, $className) {
244 // We do not need locking if we are in earlyCache mode
245 $didLock = FALSE;
246 if (!$this->isEarlyCache) {
247 $didLock = $this->acquireLock();
248 }
249
250 // Look again into the cache after we got the lock, data might have been generated meanwhile
251 $classLoadingInformation = $this->getClassLoadingInformationFromCache($cacheEntryIdentifier);
252 // Handle repeated cache miss
253 if ($classLoadingInformation === FALSE) {
254 // Generate class information
255 $classLoadingInformation = $this->buildClassLoadingInformation($className);
256
257 if ($classLoadingInformation !== FALSE) {
258 // If we found class information, cache it
259 $this->classesCache->set(
260 $cacheEntryIdentifier,
261 implode("\xff", $classLoadingInformation),
262 $this->isEarlyCache ? array('early') : array()
263 );
264 } elseif (!$this->isEarlyCache) {
265 if ($this->context->isProduction()) {
266 // Cache that the class is unknown
267 $this->classesCache->set($cacheEntryIdentifier, '');
268 }
269 }
270 }
271
272 $this->releaseLock($didLock);
273
274 return $classLoadingInformation;
275 }
276
277 /**
278 * Builds the class loading information
279 *
280 * @param string $className Name of class this information is for
281 *
282 * @return array|FALSE The class information or FALSE if class was not found
283 */
284 public function buildClassLoadingInformation($className) {
285 $classLoadingInformation = $this->buildClassLoadingInformationForClassFromCorePackage($className);
286
287 if ($classLoadingInformation === FALSE) {
288 $classLoadingInformation = $this->fetchClassLoadingInformationFromRuntimeCache($className);
289 }
290
291 if ($classLoadingInformation === FALSE) {
292 $classLoadingInformation = $this->buildClassLoadingInformationForClassFromRegisteredPackages($className);
293 }
294
295 if ($classLoadingInformation === FALSE) {
296 $classLoadingInformation = $this->buildClassLoadingInformationForClassByNamingConvention($className);
297 }
298
299 return $classLoadingInformation;
300 }
301
302 /**
303 * Find out if a class name is valid
304 *
305 * @param string $className
306 * @return bool
307 */
308 protected function isValidClassName($className) {
309 return (bool)preg_match(self::VALID_CLASSNAME_PATTERN, $className);
310 }
311
312 /**
313 * Retrieve class loading information for class from core package
314 *
315 * @param string $className
316 * @return array|FALSE
317 */
318 protected function buildClassLoadingInformationForClassFromCorePackage($className) {
319 if (substr($className, 0, 14) === 'TYPO3\\CMS\\Core') {
320 $classesFolder = substr($className, 15, 5) === 'Tests' ? '' : 'Classes/';
321 $classFilePath = PATH_typo3 . 'sysext/core/' . $classesFolder . str_replace('\\', '/', substr($className, 15)) . '.php';
322 if (@file_exists($classFilePath)) {
323 return array($classFilePath, $className);
324 }
325 }
326 return FALSE;
327 }
328
329 /**
330 * Retrieve class loading information from early class name autoload registry cache
331 *
332 * @param string $className
333 * @return array|FALSE
334 */
335 protected function fetchClassLoadingInformationFromRuntimeCache($className) {
336 $lowercasedClassName = strtolower($className);
337 if (!isset($this->runtimeClassLoadingInformationCache[$lowercasedClassName])) {
338 return FALSE;
339 }
340 $classInformation = $this->runtimeClassLoadingInformationCache[$lowercasedClassName];
341 return @file_exists($classInformation[0]) ? $classInformation : FALSE;
342 }
343
344 /**
345 * Retrieve class loading information from registered packages
346 *
347 * @param string $className
348 * @return array|FALSE
349 */
350 protected function buildClassLoadingInformationForClassFromRegisteredPackages($className) {;
351 foreach ($this->packageNamespaces as $packageNamespace => $packageData) {
352 if (substr(str_replace('_', '\\', $className), 0, $packageData['namespaceLength']) === $packageNamespace) {
353 if ($packageData['substituteNamespaceInPath']) {
354 // If it's a TYPO3 package, classes don't comply to PSR-0.
355 // The namespace part is substituted.
356 $classPathAndFilename = '/' . str_replace('\\', '/', ltrim(substr($className, $packageData['namespaceLength']), '\\')) . '.php';
357 } else {
358 // Make the classname PSR-0 compliant by replacing underscores only in the classname not in the namespace
359 $classPathAndFilename = '';
360 $lastNamespacePosition = strrpos($className, '\\');
361 if ($lastNamespacePosition !== FALSE) {
362 $namespace = substr($className, 0, $lastNamespacePosition);
363 $className = substr($className, $lastNamespacePosition + 1);
364 $classPathAndFilename = str_replace('\\', '/', $namespace) . '/';
365 }
366 $classPathAndFilename .= str_replace('_', '/', $className) . '.php';
367 }
368 if (strtolower(substr($className, $packageData['namespaceLength'], 5)) === 'tests') {
369 $classPathAndFilename = $packageData['packagePath'] . $classPathAndFilename;
370 } else {
371 $classPathAndFilename = $packageData['classesPath'] . $classPathAndFilename;
372 }
373 if (@file_exists($classPathAndFilename)) {
374 return array($classPathAndFilename, $className);
375 }
376 }
377 }
378 return FALSE;
379 }
380
381 /**
382 * Retrieve class loading information based on 'extbase' naming convention into the registry.
383 *
384 * @param string $className Class name to find source file of
385 * @return array|FALSE
386 */
387 protected function buildClassLoadingInformationForClassByNamingConvention($className) {
388 $delimiter = '_';
389 // To handle namespaced class names, split the class name at the
390 // namespace delimiters.
391 if (strpos($className, '\\') !== FALSE) {
392 $delimiter = '\\';
393 }
394
395 $classNameParts = explode($delimiter, $className, 4);
396
397 // We only handle classes that follow the convention Vendor\Product\Classname or is longer
398 // so we won't deal with class names that only have one or two parts
399 if (count($classNameParts) <= 2) {
400 return FALSE;
401 }
402
403 if (
404 isset($classNameParts[0])
405 && isset($classNameParts[1])
406 && $classNameParts[0] === 'TYPO3'
407 && $classNameParts[1] === 'CMS'
408 ) {
409 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[2]);
410 $classNameWithoutVendorAndProduct = $classNameParts[3];
411 } else {
412 $extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored($classNameParts[1]);
413 $classNameWithoutVendorAndProduct = $classNameParts[2];
414
415 if (isset($classNameParts[3])) {
416 $classNameWithoutVendorAndProduct .= $delimiter . $classNameParts[3];
417 }
418 }
419
420 if ($extensionKey && isset($this->packageClassesPaths[$extensionKey])) {
421 if (substr(strtolower($classNameWithoutVendorAndProduct), 0, 5) === 'tests') {
422 $classesPath = $this->packages[$extensionKey]->getPackagePath();
423 } else {
424 $classesPath = $this->packageClassesPaths[$extensionKey];
425 }
426 $classFilePath = $classesPath . strtr($classNameWithoutVendorAndProduct, $delimiter, '/') . '.php';
427 if (@file_exists($classFilePath)) {
428 return array($classFilePath, $className);
429 }
430 }
431
432 return FALSE;
433 }
434
435 /**
436 * Get cache entry identifier for the package namespaces cache
437 *
438 * @return string|NULL identifier
439 */
440 protected function getCacheEntryIdentifier() {
441 return $this->cacheIdentifier !== NULL
442 ? 'ClassLoader_' . $this->cacheIdentifier
443 : NULL;
444 }
445
446 /**
447 * Set cache identifier
448 *
449 * @param string $cacheIdentifier Cache identifier for package namespaces cache
450 * @return ClassLoader
451 */
452 public function setCacheIdentifier($cacheIdentifier) {
453 $this->cacheIdentifier = $cacheIdentifier;
454 return $this;
455 }
456
457 /**
458 * Sets the available packages
459 *
460 * @param array $packages An array of \TYPO3\Flow\Package\Package objects
461 * @return ClassLoader
462 */
463 public function setPackages(array $packages) {
464 $this->packages = $packages;
465
466 if (!$this->loadPackageNamespacesFromCache()) {
467 $this->buildPackageNamespacesAndClassesPaths();
468 } else {
469 $this->classAliasMap->setPackages($packages);
470 }
471 // Clear the runtime cache for runtime activated packages
472 $this->runtimeClassLoadingInformationCache = array();
473 return $this;
474 }
475
476 /**
477 * Add a package to class loader just during runtime, so classes can be loaded without the need for a new request
478 *
479 * @param \TYPO3\Flow\Package\PackageInterface $package
480 * @return ClassLoader
481 */
482 public function addActivePackage(\TYPO3\Flow\Package\PackageInterface $package) {
483 $packageKey = $package->getPackageKey();
484 if (!isset($this->packages[$packageKey])) {
485 $this->packages[$packageKey] = $package;
486 $this->buildPackageNamespaceAndClassesPath($package);
487 $this->sortPackageNamespaces();
488 $this->loadClassFilesFromAutoloadRegistryIntoRuntimeClassInformationCache(array($package));
489 }
490 return $this;
491 }
492
493 /**
494 * Builds the package namespaces and classes paths for the given packages
495 *
496 * @throws \Exception
497 * @return void
498 */
499 protected function buildPackageNamespacesAndClassesPaths() {
500 $didLock = $this->acquireLock();
501
502 // Take a look again, after lock is acquired
503 if (!$this->loadPackageNamespacesFromCache()) {
504 try {
505 foreach ($this->packages as $package) {
506 $this->buildPackageNamespaceAndClassesPath($package);
507 }
508 $this->sortPackageNamespaces();
509 $this->savePackageNamespacesAndClassesPathsToCache();
510 // The class alias map has to be rebuilt first, because ext_autoload files can contain
511 // old class names that need established class aliases.
512 $classNameToAliasMapping = $this->classAliasMap->setPackages($this->packages)->buildMappingAndInitializeEarlyInstanceMapping();
513 $this->loadClassFilesFromAutoloadRegistryIntoRuntimeClassInformationCache($this->packages);
514 $this->classAliasMap->buildMappingFiles($classNameToAliasMapping);
515 $this->transferRuntimeClassInformationCacheEntriesToClassesCache();
516 } catch (\Exception $e) {
517 // Catching all Exceptions, as we don't know where in the process the class cache building breaks we
518 // need to clear our cache and also release our lock before we throw the Exception again to the user.
519 $this->clearClassesCache();
520 $this->releaseLock($didLock);
521 throw $e;
522 }
523 }
524
525 $this->releaseLock($didLock);
526 }
527
528 /**
529 * Builds the namespace and class paths for a single package
530 *
531 * @param \TYPO3\Flow\Package\PackageInterface $package
532 * @return void
533 */
534 protected function buildPackageNamespaceAndClassesPath(\TYPO3\Flow\Package\PackageInterface $package) {
535 if ($package instanceof \TYPO3\Flow\Package\PackageInterface) {
536 $this->buildPackageNamespace($package);
537 }
538 if ($package instanceof PackageInterface) {
539 $this->buildPackageClassPathsForLegacyExtension($package);
540 }
541 }
542
543 /**
544 * Load package namespaces from cache
545 *
546 * @return bool TRUE if package namespaces were loaded
547 */
548 protected function loadPackageNamespacesFromCache() {
549 $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
550 if ($cacheEntryIdentifier === NULL) {
551 return FALSE;
552 }
553 $packageData = $this->coreCache->requireOnce($cacheEntryIdentifier);
554 if ($packageData !== FALSE) {
555 list($packageNamespaces, $packageClassesPaths) = $packageData;
556 if (is_array($packageNamespaces) && is_array($packageClassesPaths)) {
557 $this->packageNamespaces = $packageNamespaces;
558 $this->packageClassesPaths = $packageClassesPaths;
559 return TRUE;
560 }
561 }
562 return FALSE;
563 }
564
565 /**
566 * Extracts the namespace from a package
567 *
568 * @param \TYPO3\Flow\Package\PackageInterface $package
569 * @return void
570 */
571 protected function buildPackageNamespace(\TYPO3\Flow\Package\PackageInterface $package) {
572 $packageNamespace = $package->getNamespace();
573 // Ignore legacy extensions with unknown vendor name
574 if ($packageNamespace[0] !== '*') {
575 $this->packageNamespaces[$packageNamespace] = array(
576 'namespaceLength' => strlen($packageNamespace),
577 'classesPath' => $package->getClassesPath(),
578 'packagePath' => $package->getPackagePath(),
579 'substituteNamespaceInPath' => ($package instanceof PackageInterface)
580 );
581 }
582 }
583
584 /**
585 * Save autoload registry to cache
586 *
587 * @param array $packages
588 * @return void
589 */
590 protected function loadClassFilesFromAutoloadRegistryIntoRuntimeClassInformationCache(array $packages) {
591 $classFileAutoloadRegistry = array();
592 foreach ($packages as $package) {
593 if ($package instanceof PackageInterface) {
594 $classFilesFromAutoloadRegistry = $package->getClassFilesFromAutoloadRegistry();
595 if (is_array($classFilesFromAutoloadRegistry)) {
596 $classFileAutoloadRegistry = array_merge($classFileAutoloadRegistry, $classFilesFromAutoloadRegistry);
597 }
598 }
599 }
600 foreach ($classFileAutoloadRegistry as $className => $classFilePath) {
601 $lowercasedClassName = strtolower($className);
602 if (!isset($this->runtimeClassLoadingInformationCache[$lowercasedClassName]) && @file_exists($classFilePath)) {
603 $this->runtimeClassLoadingInformationCache[$lowercasedClassName] = array($classFilePath, $className);
604 }
605 }
606 }
607
608 /**
609 * Transfers all entries from the early class information cache to
610 * the classes cache in order to make them persistent
611 *
612 * @return void
613 */
614 protected function transferRuntimeClassInformationCacheEntriesToClassesCache() {
615 foreach ($this->runtimeClassLoadingInformationCache as $classLoadingInformation) {
616 $cacheEntryIdentifier = strtolower(str_replace('\\', '_', $classLoadingInformation[1]));
617 if (!$this->classesCache->has($cacheEntryIdentifier)) {
618 $this->classesCache->set($cacheEntryIdentifier, implode("\xff", $classLoadingInformation));
619 }
620 }
621 }
622
623 /**
624 * @param PackageInterface $package
625 * @return void
626 */
627 protected function buildPackageClassPathsForLegacyExtension(PackageInterface $package) {
628 $this->packageClassesPaths[$package->getPackageKey()] = $package->getClassesPath();
629 foreach ($package->getPackageReplacementKeys() as $packageToReplace => $_) {
630 $this->packageClassesPaths[$packageToReplace] = $package->getClassesPath();
631 }
632 }
633
634 /**
635 * Save package namespaces and classes paths to cache
636 *
637 * @return void
638 */
639 protected function savePackageNamespacesAndClassesPathsToCache() {
640 $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
641 if ($cacheEntryIdentifier !== NULL) {
642 $this->coreCache->set(
643 $this->getCacheEntryIdentifier(),
644 'return ' . var_export(array($this->packageNamespaces, $this->packageClassesPaths), TRUE) . ';'
645 );
646 }
647 }
648
649 /**
650 * Sorts longer package namespaces first, to find specific matches before generic ones
651 *
652 * @return void
653 */
654 protected function sortPackageNamespaces() {
655 $sortPackages = function ($a, $b) {
656 if (($lenA = strlen($a)) === ($lenB = strlen($b))) {
657 return strcmp($a, $b);
658 }
659 return $lenA > $lenB ? -1 : 1;
660 };
661 uksort($this->packageNamespaces, $sortPackages);
662 }
663
664 /**
665 * This method is necessary for the early loading of the cores autoload registry
666 *
667 * @param array $classFileAutoloadRegistry
668 * @return void
669 */
670 public function setRuntimeClassLoadingInformationFromAutoloadRegistry(array $classFileAutoloadRegistry) {
671 foreach ($classFileAutoloadRegistry as $className => $classFilePath) {
672 $lowercasedClassName = strtolower($className);
673 if (!isset($this->runtimeClassLoadingInformationCache[$lowercasedClassName])) {
674 $this->runtimeClassLoadingInformationCache[$lowercasedClassName] = array($classFilePath, $className);
675 }
676 }
677 }
678
679 /**
680 * Set alias for class name
681 *
682 * @param string $aliasClassName
683 * @param string $originalClassName
684 * @return bool
685 */
686 public function setAliasForClassName($aliasClassName, $originalClassName) {
687 return $this->classAliasMap->setAliasForClassName($aliasClassName, $originalClassName);
688 }
689
690 /**
691 * Get class name for alias
692 *
693 * @param string $alias
694 * @return mixed
695 */
696 static public function getClassNameForAlias($alias) {
697 return static::$staticAliasMap->getClassNameForAlias($alias);
698 }
699
700 /**
701 * Get an aliases for a class name
702 *
703 * @param string $className
704 * @return mixed
705 */
706 static public function getAliasesForClassName($className) {
707 return static::$staticAliasMap->getAliasesForClassName($className);
708 }
709
710 /**
711 * Acquires a lock for the cache if we didn't already lock before.
712 *
713 * @return bool TRUE if the cache was acquired by this call and needs to be released
714 * @throws \RuntimeException
715 */
716 protected function acquireLock() {
717 if (!$this->isLoadingLocker) {
718 $lockObject = $this->getLocker();
719
720 if ($lockObject === NULL) {
721 // During installation typo3temp does not yet exist, so the locker can not
722 // do its job. In this case it does not need to be released again.
723 return FALSE;
724 }
725
726 // We didn't lock yet so do it
727 if (!$lockObject->getLockStatus()) {
728 if (!$lockObject->acquireExclusiveLock()) {
729 throw new \RuntimeException('Could not acquire lock for ClassLoader cache creation.', 1394480725);
730 }
731
732 if (!$this->shutdownRegistered) {
733 $this->shutdownRegistered = TRUE;
734 register_shutdown_function(array($this, 'checkForCrashAndCleanup'));
735 }
736
737 return TRUE;
738 }
739 }
740 return FALSE;
741 }
742
743 /**
744 * Clean the cache and release lock
745 *
746 * If building the cache for classes failed, we don't know in which state we are. So we need to clear the cache
747 * completely and remove the lock which should exist.
748 * If the PHP process receives either the SIGTERM or SIGKILL signal, this function will NOT be called.
749 * It might still be called on a SIGSEGV signal though, but we can't trust the members of this class then,
750 * hence, the contents of $this->* might be nonsense and its usage might lead to undesired behavior.
751 *
752 * This function needs to be public, so it can be called as shutdown-function, but this function may be changed,
753 * renamed or deleted without deprecation, also in bugfix-releases.
754 *
755 * @return void
756 * @internal
757 */
758 public function checkForCrashAndCleanup() {
759 // As we are used as shutdownFunction we need to test if we get called while the lock is set.
760 // If this is the case, the cache creation has crashed.
761 $error = error_get_last();
762
763 // $this->lockObject can be null in installer context without typo3temp, but then this method shouldn't
764 // be registered as shutdown-function due to caching being disabled in this case.
765 // See @getLocker for more information.
766 if ($error !== NULL && $this->lockObject !== NULL && $this->lockObject->getLockStatus()) {
767 $this->clearClassesCache();
768 $this->releaseLock(TRUE);
769 }
770 }
771
772 /**
773 * Releases a lock
774 *
775 * @param bool $needRelease The result of the call to acquireLock()
776 * @return void
777 */
778 protected function releaseLock($needRelease) {
779 if ($needRelease) {
780 $lockObject = $this->getLocker();
781 $lockObject->release();
782 }
783 }
784
785 /**
786 * Cleares the complete cache for class loader.
787 *
788 * @return void
789 */
790 protected function clearClassesCache() {
791 $this->coreCache->flush();
792 $this->classesCache->flush();
793 }
794
795 /**
796 * Gets the TYPO3 Locker object or creates an instance of it.
797 *
798 * @throws \RuntimeException
799 * @return \TYPO3\CMS\Core\Locking\Locker|NULL Only NULL if we are in installer and typo3temp does not exist yet
800 */
801 protected function getLocker() {
802 if (NULL === $this->lockObject) {
803 $this->isLoadingLocker = TRUE;
804
805 try {
806 $this->lockObject = new Locker('ClassLoader-cache-classes', Locker::LOCKING_METHOD_SIMPLE);
807 } catch (\RuntimeException $e) {
808 // The RuntimeException in constructor happens if directory typo3temp/locks could not be created.
809 // This usually happens during installation step 1 where typo3temp itself does not exist yet. In
810 // this case we proceed without locking, otherwise a missing typo3temp directory indicates a
811 // hard problem of the instance and we throw up.
812 // @TODO: This solution currently conflicts with separation of concerns since the class loader
813 // handles installation specific stuff. Find a better way to do this.
814 if (defined('TYPO3_enterInstallScript') && TYPO3_enterInstallScript) {
815 // Installer is running => So work without Locking.
816 return NULL;
817 } else {
818 throw $e;
819 }
820 }
821 $this->lockObject->setEnableLogging(FALSE);
822 $this->isLoadingLocker = FALSE;
823 }
824
825 return $this->lockObject;
826 }
827
828 }