[BUGFIX] Clean up and fix class loading
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Core / ClassLoadingInformationGenerator.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 Composer\Autoload\ClassMapGenerator;
18 use TYPO3\CMS\Core\Package\Exception\MissingPackageManifestException;
19 use TYPO3\CMS\Core\Package\Package;
20 use TYPO3\CMS\Core\Package\PackageManager;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\PathUtility;
23
24 /**
25 * Generates class loading information (class maps, class aliases etc.) and writes it to files
26 * for further inclusion in the bootstrap
27 */
28 class ClassLoadingInformationGenerator {
29
30 /**
31 * @var Package[]
32 */
33 static protected $activeExtensionPackages;
34
35 /**
36 * Returns class loading information for a single package
37 *
38 * @param Package $package The package to generate the class loading info for
39 * @param bool $useRelativePaths If set to TRUE, make the path relative to the current TYPO3 instance (PATH_site)
40 * @return array
41 */
42 static public function buildClassLoadingInformationForPackage(Package $package, $useRelativePaths = FALSE) {
43 $classMap = array();
44 $psr4 = array();
45 $packagePath = $package->getPackagePath();
46
47 try {
48 $manifest = self::getPackageManager()->getComposerManifest($package->getPackagePath());
49 if (!empty($manifest->autoload->{'psr-4'})) {
50 $psr4manifest = json_decode(json_encode($manifest->autoload->{'psr-4'}), TRUE);
51 if (is_array($psr4manifest)) {
52 foreach ($psr4manifest as $namespacePrefix => $path) {
53 $namespacePath = $packagePath . $path;
54 if ($useRelativePaths) {
55 $psr4[$namespacePrefix] = self::makePathRelative($namespacePath, realpath($namespacePath));
56 } else {
57 $psr4[$namespacePrefix] = $namespacePath;
58 }
59 }
60 }
61 }
62 } catch (MissingPackageManifestException $e) {
63 // Ignore missing composer manifest
64 }
65
66 foreach (ClassMapGenerator::createMap($packagePath) as $class => $path) {
67 if ($useRelativePaths) {
68 $classMap[$class] = self::makePathRelative($packagePath, $path);
69 } else {
70 $classMap[$class] = $path;
71 }
72 }
73
74 return array('classMap' => $classMap, 'psr-4' => $psr4);
75 }
76
77 /**
78 * Returns class alias map for given package
79 *
80 * @param Package $package The package to generate the class alias info for
81 * @throws \TYPO3\CMS\Core\Error\Exception
82 * @return array
83 */
84 static public function buildClassAliasMapForPackage(Package $package) {
85 $aliasToClassNameMapping = array();
86 $classNameToAliasMapping = array();
87 $possibleClassAliasFile = $package->getPackagePath() . 'Migrations/Code/ClassAliasMap.php';
88 if (file_exists($possibleClassAliasFile)) {
89 $packageAliasMap = require $possibleClassAliasFile;
90 if (!is_array($packageAliasMap)) {
91 throw new \TYPO3\CMS\Core\Error\Exception('"class alias maps" must return an array', 1422625075);
92 }
93 foreach ($packageAliasMap as $aliasClassName => $className) {
94 $lowerCasedAliasClassName = strtolower($aliasClassName);
95 $aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
96 $classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
97 }
98 }
99
100 return array('aliasToClassNameMapping' => $aliasToClassNameMapping, 'classNameToAliasMapping' => $classNameToAliasMapping);
101 }
102
103 /**
104 * Generate the class map file
105 * @return string[]
106 * @internal
107 */
108 static public function buildAutoloadInformationFiles() {
109 // Ensure that for each re-build, the packages are fetched again from the package manager
110 self::$activeExtensionPackages = NULL;
111
112 $psr4File = $classMapFile = <<<EOF
113 <?php
114
115 // autoload_classmap.php @generated by TYPO3
116
117 \$typo3InstallDir = PATH_site;
118
119 return array(
120
121 EOF;
122 $classMap = array();
123 $psr4 = array();
124 foreach (self::getActiveExtensionPackages() as $package) {
125 $classLoadingInformation = self::buildClassLoadingInformationForPackage($package, TRUE);
126 $classMap = array_merge($classMap, $classLoadingInformation['classMap']);
127 $psr4 = array_merge($psr4, $classLoadingInformation['psr-4']);
128 }
129
130 ksort($classMap);
131 ksort($psr4);
132 foreach ($classMap as $class => $relativePath) {
133 $classMapFile .= sprintf(' %s => %s,', var_export($class, TRUE), self::getPathCode($relativePath)) . LF;
134 }
135 $classMapFile .= ");\n";
136
137 foreach ($psr4 as $prefix => $relativePath) {
138 $psr4File .= sprintf(' %s => array(%s),', var_export($prefix, TRUE), self::getPathCode($relativePath)) . LF;
139 }
140 $psr4File .= ");\n";
141
142 return array('classMapFile' => $classMapFile, 'psr-4File' => $psr4File);
143 }
144
145 /**
146 * Generate a relative path string from an absolute path within a give package path
147 *
148 * @param string $packagePath
149 * @param string $realPathOfClassFile
150 * @return string
151 */
152 static protected function makePathRelative($packagePath, $realPathOfClassFile) {
153 $realPathOfClassFile = GeneralUtility::fixWindowsFilePath($realPathOfClassFile);
154 $classesRealPath = GeneralUtility::fixWindowsFilePath(realpath($packagePath));
155 $relativeClassesPath = rtrim(PathUtility::stripPathSitePrefix($packagePath), '/');
156 $relativePathToClassFile = $relativeClassesPath . '/' . ltrim(substr($realPathOfClassFile, strlen($classesRealPath)), '/');
157
158 return $relativePathToClassFile;
159 }
160
161 /**
162 * Generate a relative path string from a relative path
163 *
164 * @param string $relativePathToClassFile
165 * @return string
166 */
167 static protected function getPathCode($relativePathToClassFile) {
168 return '$typo3InstallDir . ' . var_export($relativePathToClassFile, TRUE);
169 }
170
171 /**
172 * Build class alias mapping file
173 *
174 * @return string
175 * @throws \Exception
176 * @internal
177 */
178 static public function buildClassAliasMapFile() {
179 $aliasToClassNameMapping = array();
180 $classNameToAliasMapping = array();
181 foreach (self::getActiveExtensionPackages() as $package) {
182 $aliasMappingForPackage = self::buildClassAliasMapForPackage($package);
183 $aliasToClassNameMapping = array_merge($aliasToClassNameMapping, $aliasMappingForPackage['aliasToClassNameMapping']);
184 $classNameToAliasMapping = array_merge($classNameToAliasMapping, $aliasMappingForPackage['classNameToAliasMapping']);
185 }
186 $exportArray = array(
187 'aliasToClassNameMapping' => $aliasToClassNameMapping,
188 'classNameToAliasMapping' => $classNameToAliasMapping
189 );
190 $fileContent = '<?php' . chr(10) . 'return ';
191 $fileContent .= var_export($exportArray, TRUE);
192 $fileContent .= ";\n";
193 return $fileContent;
194 }
195
196 /**
197 * Get all packages except the protected ones, as they are covered already
198 *
199 * @return Package[]
200 */
201 static protected function getActiveExtensionPackages() {
202 if (self::$activeExtensionPackages === NULL) {
203 self::$activeExtensionPackages = array();
204 foreach (self::getPackageManager()->getActivePackages() as $package) {
205 if (self::isFrameworkPackage($package)) {
206 // Skip all core packages as the class loading info is prepared for them already
207 continue;
208 }
209 self::$activeExtensionPackages[] = $package;
210 }
211 }
212
213 return self::$activeExtensionPackages;
214 }
215
216 /**
217 * Check if the package is a framework package (located in typo3/sysext)
218 *
219 * @param Package $package
220 * @return bool
221 */
222 static protected function isFrameworkPackage(Package $package) {
223 return $package->getValueFromComposerManifest('type') === 'typo3-cms-framework';
224 }
225
226 /**
227 * @return PackageManager
228 * @throws \TYPO3\CMS\Core\Exception
229 */
230 static protected function getPackageManager() {
231 return Bootstrap::getInstance()->getEarlyInstance(PackageManager::class);
232 }
233
234 }