From f654ab772fa572f761bcfd1af573848f0e2b9478 Mon Sep 17 00:00:00 2001
From: Helmut Hummel <helmut.hummel@typo3.org>
Date: Wed, 30 Sep 2015 19:56:47 +0200
Subject: [PATCH] [TASK] Refactor class information generator to be testable

Add a simple test as benefit.

Resolves: #70233
Releases: master
Change-Id: I92d093261d6c5909dbe91cf3661ae8cfa852216d
Reviewed-on: http://review.typo3.org/43668
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Classes/Core/ClassLoadingInformation.php  | 34 ++++++--
 .../Core/ClassLoadingInformationGenerator.php | 84 +++++++------------
 .../ClassLoadingInformationGeneratorTest.php  | 37 +++++++-
 .../test_extension/Resources/PHP/Test.php     | 19 +++++
 .../Fixtures/test_extension/composer.json     | 20 +++++
 .../Fixtures/test_extension/ext_emconf.php    | 19 +++++
 6 files changed, 151 insertions(+), 62 deletions(-)
 create mode 100644 typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/Resources/PHP/Test.php
 create mode 100644 typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/composer.json
 create mode 100644 typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/ext_emconf.php

diff --git a/typo3/sysext/core/Classes/Core/ClassLoadingInformation.php b/typo3/sysext/core/Classes/Core/ClassLoadingInformation.php
index e3704e3d4698..1525c3603333 100644
--- a/typo3/sysext/core/Classes/Core/ClassLoadingInformation.php
+++ b/typo3/sysext/core/Classes/Core/ClassLoadingInformation.php
@@ -14,9 +14,10 @@ namespace TYPO3\CMS\Core\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Composer\Autoload\ClassLoader as ComposerClassLoader;
+use Composer\Autoload\ClassLoader;
 use Helhum\ClassAliasLoader\ClassAliasMap;
 use TYPO3\CMS\Core\Package\PackageInterface;
+use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -69,8 +70,11 @@ class ClassLoadingInformation {
 	 */
 	static public function dumpClassLoadingInformation() {
 		self::ensureAutoloadInfoDirExists();
+		$composerClassLoader = static::getClassLoader();
+		$activeExtensionPackages = static::getActiveExtensionPackages();
+
 		/** @var ClassLoadingInformationGenerator  $generator */
-		$generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class);
+		$generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, PATH_site);
 		$classInfoFiles = $generator->buildAutoloadInformationFiles();
 		GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME, $classInfoFiles['classMapFile']);
 		GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME, $classInfoFiles['psr-4File']);
@@ -121,9 +125,10 @@ class ClassLoadingInformation {
 	 */
 	static public function registerTransientClassLoadingInformationForPackage(PackageInterface $package) {
 		$composerClassLoader = static::getClassLoader();
+		$activeExtensionPackages = static::getActiveExtensionPackages();
 
 		/** @var ClassLoadingInformationGenerator  $generator */
-		$generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class);
+		$generator = GeneralUtility::makeInstance(ClassLoadingInformationGenerator::class, $composerClassLoader, $activeExtensionPackages, PATH_site);
 
 		$classInformation = $generator->buildClassLoadingInformationForPackage($package);
 		$composerClassLoader->addClassMap($classInformation['classMap']);
@@ -173,11 +178,30 @@ class ClassLoadingInformation {
 	/**
 	 * Internal method calling the bootstrap to fetch the composer class loader
 	 *
-	 * @return ComposerClassLoader
+	 * @return ClassLoader
 	 * @throws \TYPO3\CMS\Core\Exception
 	 */
 	static protected function getClassLoader() {
-		return Bootstrap::getInstance()->getEarlyInstance(ComposerClassLoader::class);
+		return Bootstrap::getInstance()->getEarlyInstance(ClassLoader::class);
+	}
+
+	/**
+	 * Get all packages except the protected ones, as they are covered already
+	 *
+	 * @return PackageInterface[]
+	 */
+	static protected function getActiveExtensionPackages() {
+		$activeExtensionPackages = [];
+		/** @var PackageManager $packageManager */
+		$packageManager = Bootstrap::getInstance()->getEarlyInstance(PackageManager::class);
+		foreach ($packageManager->getActivePackages() as $package) {
+			if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') {
+				// Skip all core packages as the class loading info is prepared for them already
+				continue;
+			}
+			$activeExtensionPackages[] = $package;
+		}
+		return $activeExtensionPackages;
 	}
 
 }
diff --git a/typo3/sysext/core/Classes/Core/ClassLoadingInformationGenerator.php b/typo3/sysext/core/Classes/Core/ClassLoadingInformationGenerator.php
index 69200914b7c5..05e5116b743e 100644
--- a/typo3/sysext/core/Classes/Core/ClassLoadingInformationGenerator.php
+++ b/typo3/sysext/core/Classes/Core/ClassLoadingInformationGenerator.php
@@ -15,15 +15,14 @@ namespace TYPO3\CMS\Core\Core;
  */
 
 use Composer\Autoload\ClassMapGenerator;
-use Composer\Autoload\ClassLoader as ComposerClassLoader;
+use Composer\Autoload\ClassLoader;
 use TYPO3\CMS\Core\Package\PackageInterface;
-use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
  * Generates class loading information (class maps, class aliases etc.) and writes it to files
  * for further inclusion in the bootstrap
+ * @internal
  */
 class ClassLoadingInformationGenerator {
 
@@ -32,6 +31,27 @@ class ClassLoadingInformationGenerator {
 	 */
 	protected $activeExtensionPackages;
 
+	/**
+	 * @var ClassLoader
+	 */
+	protected $classLoader;
+
+	/**
+	 * @var string
+	 */
+	protected $installationRoot;
+
+	/**
+	 * @param ClassLoader $classLoader
+	 * @param array $activeExtensionPackages
+	 * @param string $installationRoot
+	 */
+	public function __construct(ClassLoader $classLoader, array $activeExtensionPackages = array(), $installationRoot) {
+		$this->classLoader = $classLoader;
+		$this->activeExtensionPackages = $activeExtensionPackages;
+		$this->installationRoot = $installationRoot;
+	}
+
 	/**
 	 * Returns class loading information for a single package
 	 *
@@ -43,8 +63,7 @@ class ClassLoadingInformationGenerator {
 		$classMap = array();
 		$psr4 = array();
 		$packagePath = $package->getPackagePath();
-
-		$manifest = $this->getPackageManager()->getComposerManifest($packagePath);
+		$manifest = $package->getValueFromComposerManifest();
 
 		if (empty($manifest->autoload)) {
 			// Legacy mode: Scan the complete extension directory for class files
@@ -52,7 +71,7 @@ class ClassLoadingInformationGenerator {
 		} else {
 			$autoloadDefinition = json_decode(json_encode($manifest->autoload), TRUE);
 			if (!empty($autoloadDefinition['psr-4']) && is_array($autoloadDefinition['psr-4'])) {
-				$classLoaderPrefixesPsr4 = $this->getClassLoader()->getPrefixesPsr4();
+				$classLoaderPrefixesPsr4 = $this->classLoader->getPrefixesPsr4();
 				foreach ($autoloadDefinition['psr-4'] as $namespacePrefix => $path) {
 					$namespacePath = $packagePath . $path;
 					if ($useRelativePaths) {
@@ -182,7 +201,7 @@ return array(
 EOF;
 		$classMap = array();
 		$psr4 = array();
-		foreach ($this->getActiveExtensionPackages() as $package) {
+		foreach ($this->activeExtensionPackages as $package) {
 			$classLoadingInformation = $this->buildClassLoadingInformationForPackage($package, TRUE);
 			$classMap = array_merge($classMap, $classLoadingInformation['classMap']);
 			$psr4 = array_merge($psr4, $classLoadingInformation['psr-4']);
@@ -214,7 +233,7 @@ EOF;
 	protected function makePathRelative($packagePath, $realPathOfClassFile, $relativeToRoot = TRUE) {
 		$realPathOfClassFile = GeneralUtility::fixWindowsFilePath($realPathOfClassFile);
 		$packageRealPath = GeneralUtility::fixWindowsFilePath(realpath($packagePath));
-		$relativePackagePath = rtrim(PathUtility::stripPathSitePrefix($packagePath), '/');
+		$relativePackagePath = rtrim(substr($packagePath, strlen($this->installationRoot)), '/');
 		if ($relativeToRoot) {
 			$relativePathToClassFile = $relativePackagePath . '/' . ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/');
 		} else {
@@ -244,7 +263,7 @@ EOF;
 	public function buildClassAliasMapFile() {
 		$aliasToClassNameMapping = array();
 		$classNameToAliasMapping = array();
-		foreach ($this->getActiveExtensionPackages() as $package) {
+		foreach ($this->activeExtensionPackages as $package) {
 			$aliasMappingForPackage = $this->buildClassAliasMapForPackage($package);
 			$aliasToClassNameMapping = array_merge($aliasToClassNameMapping, $aliasMappingForPackage['aliasToClassNameMapping']);
 			$classNameToAliasMapping = array_merge($classNameToAliasMapping, $aliasMappingForPackage['classNameToAliasMapping']);
@@ -259,51 +278,4 @@ EOF;
 		return $fileContent;
 	}
 
-	/**
-	 * Get all packages except the protected ones, as they are covered already
-	 *
-	 * @return PackageInterface[]
-	 */
-	protected function getActiveExtensionPackages() {
-		if ($this->activeExtensionPackages === NULL) {
-			$this->activeExtensionPackages = array();
-			foreach ($this->getPackageManager()->getActivePackages() as $package) {
-				if ($this->isFrameworkPackage($package)) {
-					// Skip all core packages as the class loading info is prepared for them already
-					continue;
-				}
-				$this->activeExtensionPackages[] = $package;
-			}
-		}
-
-		return $this->activeExtensionPackages;
-	}
-
-	/**
-	 * Check if the package is a framework package (located in typo3/sysext)
-	 *
-	 * @param PackageInterface $package
-	 * @return bool
-	 */
-	protected function isFrameworkPackage(PackageInterface $package) {
-		return $package->getValueFromComposerManifest('type') === 'typo3-cms-framework';
-	}
-
-	/**
-	 * @return PackageManager
-	 * @throws \TYPO3\CMS\Core\Exception
-	 */
-	protected function getPackageManager() {
-		return Bootstrap::getInstance()->getEarlyInstance(PackageManager::class);
-	}
-
-	/**
-	 * Internal method calling the bootstrap to fetch the composer class loader
-	 *
-	 * @return ComposerClassLoader
-	 * @throws \TYPO3\CMS\Core\Exception
-	 */
-	protected function getClassLoader() {
-		return Bootstrap::getInstance()->getEarlyInstance(ComposerClassLoader::class);
-	}
 }
diff --git a/typo3/sysext/core/Tests/Unit/Core/ClassLoadingInformationGeneratorTest.php b/typo3/sysext/core/Tests/Unit/Core/ClassLoadingInformationGeneratorTest.php
index cb0536a2209b..7015471c4eee 100644
--- a/typo3/sysext/core/Tests/Unit/Core/ClassLoadingInformationGeneratorTest.php
+++ b/typo3/sysext/core/Tests/Unit/Core/ClassLoadingInformationGeneratorTest.php
@@ -10,7 +10,10 @@ namespace TYPO3\CMS\Core\Tests\Unit\Core;
  *                                                                        *
  * The TYPO3 project - inspiring people to share!                         *
  *                                                                        */
+
+use Composer\Autoload\ClassLoader;
 use TYPO3\CMS\Core\Core\ClassLoadingInformationGenerator;
+use TYPO3\CMS\Core\Package\PackageInterface;
 use TYPO3\CMS\Core\Tests\UnitTestCase;
 
 
@@ -48,9 +51,41 @@ class ClassLoadingInformationGeneratorTest extends UnitTestCase {
 	 * @param bool $expectedResult
 	 */
 	public function isIgnoredClassNameIgnoresTestClasses($className, $expectedResult) {
-		$generator = $this->getAccessibleMock(ClassLoadingInformationGenerator::class, ['dummy']);
+		$generator = $this->getAccessibleMock(
+			ClassLoadingInformationGenerator::class,
+			['dummy'],
+			[$this->getMock(ClassLoader::class), $this->createPackagesMock(), __DIR__]
+		);
 
 		$this->assertEquals($expectedResult, $generator->_call('isIgnoredClassName', $className));
 	}
 
+	/**
+	 * @test
+	 */
+	public function autoloadFilesAreBuildCorrectly() {
+		/** @var ClassLoader|\PHPUnit_Framework_MockObject_MockObject $classLoaderMock */
+		$classLoaderMock = $this->getMock(ClassLoader::class);
+		$generator = new ClassLoadingInformationGenerator($classLoaderMock, $this->createPackagesMock(), __DIR__);
+		$files = $generator->buildAutoloadInformationFiles();
+
+		$this->assertArrayHasKey('psr-4File', $files);
+		$this->assertArrayHasKey('classMapFile', $files);
+		$this->assertContains('\'TYPO3\\\\CMS\\\\TestExtension\\\\\' => array($typo3InstallDir . \'/Fixtures/test_extension/Classes/\')', $files['psr-4File']);
+		$this->assertContains('$typo3InstallDir . \'/Fixtures/test_extension/Resources/PHP/Test.php\'', $files['classMapFile']);
+	}
+
+	/**
+	 * @return PackageInterface[]
+	 */
+	protected function createPackagesMock() {
+		$packageStub = $this->getMock(PackageInterface::class);
+		$packageStub->expects($this->any())->method('getPackagePath')->willReturn(__DIR__ . '/Fixtures/test_extension/');
+		$packageStub->expects($this->any())->method('getValueFromComposerManifest')->willReturn(
+			json_decode(file_get_contents(__DIR__ . '/Fixtures/test_extension/composer.json'))
+		);
+
+		return [$packageStub];
+	}
+
 }
diff --git a/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/Resources/PHP/Test.php b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/Resources/PHP/Test.php
new file mode 100644
index 000000000000..304459ed721c
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/Resources/PHP/Test.php
@@ -0,0 +1,19 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Core\Fixtures\test_extension\Resources\PHP;
+
+/*                                                                        *
+ * This script belongs to the TYPO3 Flow framework.                       *
+ *                                                                        *
+ * It is free software; you can redistribute it and/or modify it under    *
+ * the terms of the GNU Lesser General Public License, either version 3   *
+ * of the License, or (at your option) any later version.                 *
+ *                                                                        *
+ * The TYPO3 project - inspiring people to share!                         *
+ *                                                                        */
+
+/**
+ * Class Test
+ */
+class Test {
+
+}
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/composer.json b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/composer.json
new file mode 100644
index 000000000000..fb6d1c88d458
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/composer.json
@@ -0,0 +1,20 @@
+{
+	"name": "typo3/cms-core-test",
+	"type": "typo3-cms-extension",
+	"description": "TYPO3 Core Test",
+	"homepage": "https://typo3.org",
+	"license": ["GPL-2.0+"],
+
+	"require": {
+		"php" : ">=5.5.0"
+	},
+	"replace": {
+		"core": "*"
+	},
+	"autoload": {
+		"psr-4": {
+			"TYPO3\\CMS\\TestExtension\\": "Classes/"
+		},
+		"classmap": ["Resources/PHP/"]
+	}
+}
diff --git a/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/ext_emconf.php b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/ext_emconf.php
new file mode 100644
index 000000000000..29fcff435202
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/ext_emconf.php
@@ -0,0 +1,19 @@
+<?php
+$EM_CONF[$_EXTKEY] = array(
+	'title' => 'TYPO3 Core Test',
+	'description' => 'Test Extension',
+	'category' => 'be',
+	'state' => 'stable',
+	'uploadfolder' => 0,
+	'createDirs' => '',
+	'clearCacheOnLoad' => 0,
+	'author' => 'Helmut Hummel',
+	'author_email' => 'helmut@typo3.org',
+	'author_company' => '',
+	'version' => '7.6.0',
+	'constraints' => array(
+		'depends' => array(),
+		'conflicts' => array(),
+		'suggests' => array(),
+	),
+);
-- 
GitLab