42654edc194ad02006c65c2111ba0d90e81c2c98
[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\ClassLoader;
18 use Composer\Autoload\ClassMapGenerator;
19 use TYPO3\CMS\Core\Package\PackageInterface;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Generates class loading information (class maps, class aliases etc.) and writes it to files
24 * for further inclusion in the bootstrap
25 * @internal
26 */
27 class ClassLoadingInformationGenerator
28 {
29 /**
30 * @var PackageInterface[]
31 */
32 protected $activeExtensionPackages;
33
34 /**
35 * @var ClassLoader
36 */
37 protected $classLoader;
38
39 /**
40 * @var string
41 */
42 protected $installationRoot;
43
44 /**
45 * @var bool
46 */
47 protected $isDevMode;
48
49 /**
50 * @param ClassLoader $classLoader
51 * @param array $activeExtensionPackages
52 * @param string $installationRoot
53 * @param bool $isDevMode
54 */
55 public function __construct(ClassLoader $classLoader, array $activeExtensionPackages, $installationRoot, $isDevMode = false)
56 {
57 $this->classLoader = $classLoader;
58 $this->activeExtensionPackages = $activeExtensionPackages;
59 $this->installationRoot = $installationRoot;
60 $this->isDevMode = $isDevMode;
61 }
62
63 /**
64 * Returns class loading information for a single package
65 *
66 * @param PackageInterface $package The package to generate the class loading info for
67 * @param bool $useRelativePaths If set to TRUE, make the path relative to the current TYPO3 public web path
68 * @return array
69 */
70 public function buildClassLoadingInformationForPackage(PackageInterface $package, $useRelativePaths = false)
71 {
72 $classMap = [[]];
73 $psr4 = [];
74 $packagePath = $package->getPackagePath();
75 $manifest = $package->getValueFromComposerManifest();
76
77 if (empty($manifest->autoload)) {
78 // Legacy mode: Scan the complete extension directory for class files
79 $classMap[] = $this->createClassMap($packagePath, $useRelativePaths, !$this->isDevMode);
80 } else {
81 $autoloadPsr4 = $this->getAutoloadSectionFromManifest($manifest, 'psr-4');
82 if (!empty($autoloadPsr4)) {
83 foreach ($autoloadPsr4 as $namespacePrefix => $paths) {
84 foreach ((array)$paths as $path) {
85 $namespacePath = $packagePath . $path;
86 $namespaceRealPath = realpath($namespacePath);
87 if ($useRelativePaths) {
88 $psr4[$namespacePrefix][] = $this->makePathRelative($namespacePath, $namespaceRealPath);
89 } else {
90 $psr4[$namespacePrefix][] = $namespacePath;
91 }
92 if (!empty($namespaceRealPath) && is_dir($namespaceRealPath)) {
93 // Add all prs-4 classes to the class map for improved class loading performance
94 $classMap[] = $this->createClassMap($namespacePath, $useRelativePaths, false, $namespacePrefix);
95 }
96 }
97 }
98 }
99 $autoloadClassmap = $this->getAutoloadSectionFromManifest($manifest, 'classmap');
100 if (!empty($autoloadClassmap)) {
101 foreach ($autoloadClassmap as $path) {
102 $classMap[] = $this->createClassMap($packagePath . $path, $useRelativePaths);
103 }
104 }
105 }
106
107 return ['classMap' => array_merge(...$classMap), 'psr-4' => $psr4];
108 }
109
110 /**
111 * Fetches class loading info from the according section from the manifest file.
112 * Development information will be extracted and merged as well.
113 *
114 * @param \stdClass $manifest
115 * @param string $section
116 * @return array
117 */
118 protected function getAutoloadSectionFromManifest($manifest, $section)
119 {
120 $finalAutoloadSection = [];
121 $autoloadDefinition = json_decode(json_encode($manifest->autoload), true);
122 if (!empty($autoloadDefinition[$section]) && is_array($autoloadDefinition[$section])) {
123 $finalAutoloadSection = $autoloadDefinition[$section];
124 }
125 if ($this->isDevMode) {
126 if (isset($manifest->{'autoload-dev'})) {
127 $autoloadDefinitionDev = json_decode(json_encode($manifest->{'autoload-dev'}), true);
128 if (!empty($autoloadDefinitionDev[$section]) && is_array($autoloadDefinitionDev[$section])) {
129 $finalAutoloadSection = array_merge($finalAutoloadSection, $autoloadDefinitionDev[$section]);
130 }
131 }
132 }
133
134 return $finalAutoloadSection;
135 }
136
137 /**
138 * Creates a class map for a given (absolute) path
139 *
140 * @param string $classesPath
141 * @param bool $useRelativePaths
142 * @param bool $ignorePotentialTestClasses
143 * @param string $namespace
144 * @return array
145 */
146 protected function createClassMap($classesPath, $useRelativePaths = false, $ignorePotentialTestClasses = false, $namespace = null)
147 {
148 $classMap = [];
149 $blacklistExpression = null;
150 if ($ignorePotentialTestClasses) {
151 $blacklistPathPrefix = realpath($classesPath);
152 $blacklistPathPrefix = str_replace('\\', '/', $blacklistPathPrefix);
153 $blacklistExpression = "{($blacklistPathPrefix/tests/|$blacklistPathPrefix/Tests/|$blacklistPathPrefix/Resources/|$blacklistPathPrefix/res/|$blacklistPathPrefix/class.ext_update.php)}";
154 }
155 foreach (ClassMapGenerator::createMap($classesPath, $blacklistExpression, null, $namespace) as $class => $path) {
156 if ($useRelativePaths) {
157 $classMap[$class] = $this->makePathRelative($classesPath, $path);
158 } else {
159 $classMap[$class] = $path;
160 }
161 }
162 return $classMap;
163 }
164
165 /**
166 * Returns class alias map for given package
167 *
168 * @param PackageInterface $package The package to generate the class alias info for
169 * @throws \TYPO3\CMS\Core\Error\Exception
170 * @return array
171 */
172 public function buildClassAliasMapForPackage(PackageInterface $package)
173 {
174 $aliasToClassNameMapping = [];
175 $classNameToAliasMapping = [];
176 $possibleClassAliasFiles = [];
177 $manifest = $package->getValueFromComposerManifest();
178 if (!empty($manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'})) {
179 $possibleClassAliasFiles = $manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'};
180 if (!is_array($possibleClassAliasFiles)) {
181 throw new \TYPO3\CMS\Core\Error\Exception('"typo3/class-alias-loader"/"class-alias-maps" must return an array!', 1444142481);
182 }
183 } else {
184 $possibleClassAliasFiles[] = 'Migrations/Code/ClassAliasMap.php';
185 }
186 $packagePath = $package->getPackagePath();
187 foreach ($possibleClassAliasFiles as $possibleClassAliasFile) {
188 $possiblePathToClassAliasFile = $packagePath . $possibleClassAliasFile;
189 if (file_exists($possiblePathToClassAliasFile)) {
190 $packageAliasMap = require $possiblePathToClassAliasFile;
191 if (!is_array($packageAliasMap)) {
192 throw new \TYPO3\CMS\Core\Error\Exception('"class alias maps" must return an array', 1422625075);
193 }
194 foreach ($packageAliasMap as $aliasClassName => $className) {
195 $lowerCasedAliasClassName = strtolower($aliasClassName);
196 $aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
197 $classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
198 }
199 }
200 }
201
202 return ['aliasToClassNameMapping' => $aliasToClassNameMapping, 'classNameToAliasMapping' => $classNameToAliasMapping];
203 }
204
205 /**
206 * Generate the class map file
207 * @return string[]
208 * @internal
209 */
210 public function buildAutoloadInformationFiles()
211 {
212 $psr4File = $classMapFile = <<<EOF
213 <?php
214
215 // autoload_classmap.php @generated by TYPO3
216
217 \$typo3InstallDir = \TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/';
218
219 return array(
220
221 EOF;
222 $classMap = [[]];
223 $psr4 = [[]];
224 foreach ($this->activeExtensionPackages as $package) {
225 $classLoadingInformation = $this->buildClassLoadingInformationForPackage($package, true);
226 $classMap[] = $classLoadingInformation['classMap'];
227 $psr4[] = $classLoadingInformation['psr-4'];
228 }
229 $classMap = array_merge(...$classMap);
230 $psr4 = array_merge(...$psr4);
231
232 ksort($classMap);
233 ksort($psr4);
234 foreach ($classMap as $class => $relativePath) {
235 $classMapFile .= sprintf(' %s => %s,', var_export($class, true), $this->getPathCode($relativePath)) . LF;
236 }
237 $classMapFile .= ");\n";
238
239 foreach ($psr4 as $prefix => $relativePaths) {
240 $psr4File .= sprintf(' %s => array(%s),', var_export($prefix, true), implode(',', array_map([$this, 'getPathCode'], $relativePaths))) . LF;
241 }
242 $psr4File .= ");\n";
243
244 return ['classMapFile' => $classMapFile, 'psr-4File' => $psr4File];
245 }
246
247 /**
248 * Generate a relative path string from an absolute path within a give package path
249 *
250 * @param string $packagePath
251 * @param string $realPathOfClassFile
252 * @param bool $relativeToRoot
253 * @return string
254 */
255 protected function makePathRelative($packagePath, $realPathOfClassFile, $relativeToRoot = true)
256 {
257 $realPathOfClassFile = GeneralUtility::fixWindowsFilePath($realPathOfClassFile);
258 $packageRealPath = GeneralUtility::fixWindowsFilePath(realpath($packagePath));
259 $relativePackagePath = rtrim(substr($packagePath, strlen($this->installationRoot)), '/');
260 if ($relativeToRoot) {
261 if ($realPathOfClassFile === $packageRealPath) {
262 $relativePathToClassFile = $relativePackagePath;
263 } else {
264 $relativePathToClassFile = $relativePackagePath . '/' . ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/');
265 }
266 } else {
267 $relativePathToClassFile = ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/');
268 }
269
270 return $relativePathToClassFile;
271 }
272
273 /**
274 * Generate a relative path string from a relative path
275 *
276 * @param string $relativePathToClassFile
277 * @return string
278 */
279 protected function getPathCode($relativePathToClassFile)
280 {
281 return '$typo3InstallDir . ' . var_export($relativePathToClassFile, true);
282 }
283
284 /**
285 * Build class alias mapping file
286 *
287 * @return string
288 * @throws \Exception
289 * @internal
290 */
291 public function buildClassAliasMapFile()
292 {
293 $aliasToClassNameMapping = [[]];
294 $classNameToAliasMapping = [[]];
295 foreach ($this->activeExtensionPackages as $package) {
296 $aliasMappingForPackage = $this->buildClassAliasMapForPackage($package);
297 $aliasToClassNameMapping[] = $aliasMappingForPackage['aliasToClassNameMapping'];
298 $classNameToAliasMapping[] = $aliasMappingForPackage['classNameToAliasMapping'];
299 }
300 $exportArray = [
301 'aliasToClassNameMapping' => array_merge(...$aliasToClassNameMapping),
302 'classNameToAliasMapping' => array_merge(...$classNameToAliasMapping)
303 ];
304 $fileContent = '<?php' . chr(10) . 'return ';
305 $fileContent .= var_export($exportArray, true);
306 $fileContent .= ";\n";
307 return $fileContent;
308 }
309 }