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