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