1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Core\Core; 17 18use Composer\Autoload\ClassLoader; 19use TYPO3\ClassAliasLoader\ClassAliasMap; 20use TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent; 21use TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent; 22use TYPO3\CMS\Core\Package\PackageInterface; 23use TYPO3\CMS\Core\Package\PackageManager; 24use TYPO3\CMS\Core\Utility\GeneralUtility; 25 26/** 27 * Get and manipulate class loading information, only necessary/in use 28 * when TYPO3 is not purely set up by composer but when e.g. extensions are installed via the extension manager 29 * by utilizing the composer class loader and adding more information built by the ClassLoadingInformationGenerator 30 * class. 31 * 32 * @internal 33 */ 34class ClassLoadingInformation 35{ 36 /** 37 * Base directory storing all autoload information 38 */ 39 const AUTOLOAD_INFO_DIR = 'autoload/'; 40 41 /** 42 * Base directory storing all autoload information in testing context 43 */ 44 const AUTOLOAD_INFO_DIR_TESTS = 'autoload-tests/'; 45 46 /** 47 * Name of file that contains all classes-filename mappings 48 */ 49 const AUTOLOAD_CLASSMAP_FILENAME = 'autoload_classmap.php'; 50 51 /** 52 * Name of file that contains all PSR4 mappings, fetched from the composer.json files of extensions 53 */ 54 const AUTOLOAD_PSR4_FILENAME = 'autoload_psr4.php'; 55 56 /** 57 * Name of file that contains all class alias mappings 58 */ 59 const AUTOLOAD_CLASSALIASMAP_FILENAME = 'autoload_classaliasmap.php'; 60 61 /** 62 * @var ClassLoader 63 */ 64 protected static $classLoader; 65 66 /** 67 * Sets the package manager instance 68 * 69 * @param ClassLoader $classLoader 70 * @internal 71 */ 72 public static function setClassLoader(ClassLoader $classLoader) 73 { 74 static::$classLoader = $classLoader; 75 } 76 77 /** 78 * Checks if the autoload_classmap.php exists and we are not in testing context. 79 * Used to see if the ClassLoadingInformationGenerator should be called. 80 * 81 * @return bool 82 */ 83 public static function isClassLoadingInformationAvailable() 84 { 85 return file_exists(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME); 86 } 87 88 /** 89 * Puts all information compiled by the ClassLoadingInformationGenerator to files 90 */ 91 public static function dumpClassLoadingInformation() 92 { 93 self::ensureAutoloadInfoDirExists(); 94 $composerClassLoader = static::getClassLoader(); 95 $activeExtensionPackages = static::getActiveExtensionPackages(); 96 97 /** @var ClassLoadingInformationGenerator $generator */ 98 $generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, Environment::getPublicPath() . '/', self::isTestingContext()); 99 $classInfoFiles = $generator->buildAutoloadInformationFiles(); 100 GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME, $classInfoFiles['classMapFile']); 101 GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME, $classInfoFiles['psr-4File']); 102 103 $classAliasMapFile = $generator->buildClassAliasMapFile(); 104 GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME, $classAliasMapFile); 105 } 106 107 /** 108 * @param AfterPackageDeactivationEvent $event 109 * @internal 110 */ 111 public static function updateClassLoadingInformationAfterPackageDeactivation(AfterPackageDeactivationEvent $event): void 112 { 113 if (Environment::isComposerMode()) { 114 return; 115 } 116 static::dumpClassLoadingInformation(); 117 } 118 119 /** 120 * @param AfterPackageActivationEvent $event 121 * @internal 122 */ 123 public static function updateClassLoadingInformationAfterPackageActivation(AfterPackageActivationEvent $event): void 124 { 125 if (Environment::isComposerMode()) { 126 return; 127 } 128 static::dumpClassLoadingInformation(); 129 } 130 131 /** 132 * Registers the class aliases, the class maps and the PSR4 prefixes previously identified by 133 * the ClassLoadingInformationGenerator during runtime. 134 */ 135 public static function registerClassLoadingInformation() 136 { 137 $composerClassLoader = static::getClassLoader(); 138 139 $dynamicClassAliasMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME; 140 if (file_exists($dynamicClassAliasMapFile)) { 141 $classAliasMap = require $dynamicClassAliasMapFile; 142 if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) { 143 ClassAliasMap::addAliasMap($classAliasMap); 144 } 145 } 146 147 $dynamicClassMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME; 148 if (file_exists($dynamicClassMapFile)) { 149 $classMap = require $dynamicClassMapFile; 150 if (!empty($classMap) && is_array($classMap)) { 151 $composerClassLoader->addClassMap($classMap); 152 } 153 } 154 155 $dynamicPsr4File = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME; 156 if (file_exists($dynamicPsr4File)) { 157 $psr4 = require $dynamicPsr4File; 158 if (is_array($psr4)) { 159 foreach ($psr4 as $prefix => $paths) { 160 $composerClassLoader->setPsr4($prefix, $paths); 161 } 162 } 163 } 164 } 165 166 /** 167 * Sets class loading information for a package for the current web request 168 * 169 * @param PackageInterface $package 170 * @throws \TYPO3\CMS\Core\Error\Exception 171 */ 172 public static function registerTransientClassLoadingInformationForPackage(PackageInterface $package) 173 { 174 $composerClassLoader = static::getClassLoader(); 175 $activeExtensionPackages = static::getActiveExtensionPackages(); 176 177 /** @var ClassLoadingInformationGenerator $generator */ 178 $generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, Environment::getPublicPath() . '/', self::isTestingContext()); 179 180 $classInformation = $generator->buildClassLoadingInformationForPackage($package); 181 $composerClassLoader->addClassMap($classInformation['classMap']); 182 foreach ($classInformation['psr-4'] as $prefix => $paths) { 183 $composerClassLoader->setPsr4($prefix, $paths); 184 } 185 $classAliasMap = $generator->buildClassAliasMapForPackage($package); 186 if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) { 187 ClassAliasMap::addAliasMap($classAliasMap); 188 } 189 } 190 191 /** 192 * @return string 193 */ 194 protected static function getClassLoadingInformationDirectory() 195 { 196 if (self::isTestingContext()) { 197 return Environment::getLegacyConfigPath() . '/' . self::AUTOLOAD_INFO_DIR_TESTS; 198 } 199 return Environment::getLegacyConfigPath() . '/' . self::AUTOLOAD_INFO_DIR; 200 } 201 202 /** 203 * Get class name for alias 204 * 205 * @param string $alias 206 * @return mixed 207 */ 208 public static function getClassNameForAlias($alias) 209 { 210 return ClassAliasMap::getClassNameForAlias($alias); 211 } 212 213 /** 214 * Ensures the defined path for class information files exists 215 * And clears it in case we're in testing context 216 */ 217 protected static function ensureAutoloadInfoDirExists() 218 { 219 $autoloadInfoDir = self::getClassLoadingInformationDirectory(); 220 if (!file_exists($autoloadInfoDir)) { 221 GeneralUtility::mkdir_deep($autoloadInfoDir); 222 } 223 } 224 225 /** 226 * Internal method calling the bootstrap to fetch the composer class loader 227 * 228 * @return ClassLoader 229 * @throws \TYPO3\CMS\Core\Exception 230 */ 231 protected static function getClassLoader() 232 { 233 return static::$classLoader; 234 } 235 236 /** 237 * Internal method calling the bootstrap to get application context information 238 * 239 * @return bool 240 * @throws \TYPO3\CMS\Core\Exception 241 */ 242 protected static function isTestingContext() 243 { 244 return Environment::getContext()->isTesting(); 245 } 246 247 /** 248 * Get all packages except the protected ones, as they are covered already 249 * 250 * @return PackageInterface[] 251 */ 252 protected static function getActiveExtensionPackages() 253 { 254 $activeExtensionPackages = []; 255 $packageManager = GeneralUtility::makeInstance(PackageManager::class); 256 foreach ($packageManager->getActivePackages() as $package) { 257 if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') { 258 // Skip all core packages as the class loading info is prepared for them already 259 continue; 260 } 261 $activeExtensionPackages[] = $package; 262 } 263 return $activeExtensionPackages; 264 } 265} 266