From 3daaf6c8c5764dc368a376ae58ec3d06b2398833 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Thu, 1 Mar 2018 19:19:21 +0100 Subject: [PATCH] [TASK] Merge the package DependencyResolver into the PackageManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DependencyResolver and the PackageManager have a cyclic dependency to each other which is currently resolved using GeneralUtility::makeInstance. As the DependencyResolver is actually only used for the PackageManager – and relies on it – it can be merged, saving a lot of hassle. The DependencyResolver class is not marked @internal and is therefore deprecated. Releases: master Resolves: #84109 Change-Id: I71adccec3f13eb6de859f065937fbcde369758fe Reviewed-on: https://review.typo3.org/55977 Reviewed-by: Mathias Schreiber Tested-by: Mathias Schreiber Tested-by: TYPO3com Reviewed-by: Nicole Cordes Tested-by: Nicole Cordes Reviewed-by: Christian Kuhn Tested-by: Christian Kuhn --- typo3/sysext/core/Classes/Core/Bootstrap.php | 6 +- .../Classes/Package/DependencyResolver.php | 3 +- .../core/Classes/Package/PackageManager.php | 145 +++++- ...tion-84109-DeprecateDependencyResolver.rst | 43 ++ .../Tests/Unit/Package/PackageManagerTest.php | 444 +++++++++++++++++- .../Unit/TypoScript/TemplateServiceTest.php | 1 + .../ExtensionManagementUtilityTest.php | 5 + .../Tests/Unit/Utility/GeneralUtilityTest.php | 1 + .../Package/DependencyResolverTest.php | 18 +- .../Tests/Unit/Utility/ListUtilityTest.php | 4 +- .../ExtensionScanner/Php/ClassNameMatcher.php | 5 + .../Php/MethodArgumentRequiredMatcher.php | 7 + .../Php/MethodCallMatcher.php | 9 +- 13 files changed, 650 insertions(+), 41 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-84109-DeprecateDependencyResolver.rst rename typo3/sysext/core/Tests/{Unit => UnitDeprecated}/Package/DependencyResolverTest.php (97%) diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php index 3611cbc5bfc5..837fe3666547 100644 --- a/typo3/sysext/core/Classes/Core/Bootstrap.php +++ b/typo3/sysext/core/Classes/Core/Bootstrap.php @@ -292,15 +292,13 @@ public function loadConfigurationAndInitialize($allowCaching = true, $packageMan */ public function initializePackageManagement($packageManagerClassName) { + $dependencyOrderingService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class); /** @var \TYPO3\CMS\Core\Package\PackageManager $packageManager */ - $packageManager = new $packageManagerClassName(); + $packageManager = new $packageManagerClassName($dependencyOrderingService); GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); $this->setEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); ExtensionManagementUtility::setPackageManager($packageManager); $packageManager->injectCoreCache(GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core')); - $dependencyResolver = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Package\DependencyResolver::class); - $dependencyResolver->injectDependencyOrderingService(GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class)); - $packageManager->injectDependencyResolver($dependencyResolver); $packageManager->initialize(); return $this; } diff --git a/typo3/sysext/core/Classes/Package/DependencyResolver.php b/typo3/sysext/core/Classes/Package/DependencyResolver.php index 16006a8bbbd6..91d12e605fad 100644 --- a/typo3/sysext/core/Classes/Package/DependencyResolver.php +++ b/typo3/sysext/core/Classes/Package/DependencyResolver.php @@ -21,6 +21,7 @@ * This class takes care about dependencies between packages. * It provides functionality to resolve dependencies and to determine * the crucial loading order of the packages. + * @deprecated since TYPO3 v9.2, will be removed in TYPO3 v10 */ class DependencyResolver { @@ -44,6 +45,7 @@ public function injectDependencyOrderingService(DependencyOrderingService $depen */ public function sortPackageStatesConfigurationByDependency(array $packageStatesConfiguration) { + trigger_error(self::class . ' has been deprecated with v9.2 and will be removed in TYPO3 v10.', E_USER_DEPRECATED); return $this->dependencyOrderingService->calculateOrder($this->buildDependencyGraph($packageStatesConfiguration)); } @@ -149,7 +151,6 @@ protected function buildDependencyGraph(array $packageStateConfiguration) /** * @param array $packageStateConfiguration * @return array - * @throws \TYPO3\CMS\Core\Exception */ protected function findFrameworkPackages(array $packageStateConfiguration) { diff --git a/typo3/sysext/core/Classes/Package/PackageManager.php b/typo3/sysext/core/Classes/Package/PackageManager.php index 488910eaba03..f63139fb38ee 100644 --- a/typo3/sysext/core/Classes/Package/PackageManager.php +++ b/typo3/sysext/core/Classes/Package/PackageManager.php @@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Compatibility\LoadedExtensionArrayElement; use TYPO3\CMS\Core\Core\ClassLoadingInformation; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Service\OpcodeCacheService; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -32,9 +33,9 @@ class PackageManager implements \TYPO3\CMS\Core\SingletonInterface { /** - * @var \TYPO3\CMS\Core\Package\DependencyResolver + * @var DependencyOrderingService */ - protected $dependencyResolver; + protected $dependencyOrderingService; /** * @var FrontendInterface @@ -102,11 +103,16 @@ class PackageManager implements \TYPO3\CMS\Core\SingletonInterface protected $packageStatesConfiguration = []; /** - * Constructor + * @param DependencyOrderingService $dependencyOrderingService */ - public function __construct() + public function __construct(DependencyOrderingService $dependencyOrderingService = null) { $this->packageStatesPathAndFilename = PATH_typo3conf . 'PackageStates.php'; + if ($dependencyOrderingService === null) { + trigger_error(self::class . ' without constructor based dependency injection has been deprecated in v9.2 and will not work in TYPO3 v10.', E_USER_DEPRECATED); + $dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class); + } + $this->dependencyOrderingService = $dependencyOrderingService; } /** @@ -119,10 +125,11 @@ public function injectCoreCache(FrontendInterface $coreCache) /** * @param DependencyResolver $dependencyResolver + * @deprecated */ public function injectDependencyResolver(DependencyResolver $dependencyResolver) { - $this->dependencyResolver = $dependencyResolver; + trigger_error(self::class . '::injectDependencyResolver() has been deprecated with v9.2 and will be removed in TYPO3 v10.', E_USER_DEPRECATED); } /** @@ -686,7 +693,7 @@ protected function sortActivePackagesByDependencies() // sort the packages by key at first, so we get a stable sorting of "equivalent" packages afterwards ksort($packagesWithDependencies); - $sortedPackageKeys = $this->dependencyResolver->sortPackageStatesConfigurationByDependency($packagesWithDependencies); + $sortedPackageKeys = $this->sortPackageStatesConfigurationByDependency($packagesWithDependencies); // Reorder the packages according to the loading order $this->packageStatesConfiguration['packages'] = []; @@ -1063,4 +1070,130 @@ protected function hasSubDirectories(string $path): bool { return !empty(glob(rtrim($path, '/\\') . '/*', GLOB_ONLYDIR)); } + + /** + * @param array $packageStatesConfiguration + * @return array Returns the packageStatesConfiguration sorted by dependencies + * @throws \UnexpectedValueException + */ + protected function sortPackageStatesConfigurationByDependency(array $packageStatesConfiguration) + { + return $this->dependencyOrderingService->calculateOrder($this->buildDependencyGraph($packageStatesConfiguration)); + } + + /** + * Convert the package configuration into a dependency definition + * + * This converts "dependencies" and "suggestions" to "after" syntax for the usage in DependencyOrderingService + * + * @param array $packageStatesConfiguration + * @param array $packageKeys + * @return array + * @throws \UnexpectedValueException + */ + protected function convertConfigurationForGraph(array $packageStatesConfiguration, array $packageKeys) + { + $dependencies = []; + foreach ($packageKeys as $packageKey) { + if (!isset($packageStatesConfiguration[$packageKey]['dependencies']) && !isset($packageStatesConfiguration[$packageKey]['suggestions'])) { + continue; + } + $dependencies[$packageKey] = [ + 'after' => [] + ]; + if (isset($packageStatesConfiguration[$packageKey]['dependencies'])) { + foreach ($packageStatesConfiguration[$packageKey]['dependencies'] as $dependentPackageKey) { + if (!in_array($dependentPackageKey, $packageKeys, true)) { + throw new \UnexpectedValueException( + 'The package "' . $packageKey . '" depends on "' + . $dependentPackageKey . '" which is not present in the system.', + 1519931815 + ); + } + $dependencies[$packageKey]['after'][] = $dependentPackageKey; + } + } + if (isset($packageStatesConfiguration[$packageKey]['suggestions'])) { + foreach ($packageStatesConfiguration[$packageKey]['suggestions'] as $suggestedPackageKey) { + // skip suggestions on not existing packages + if (in_array($suggestedPackageKey, $packageKeys, true)) { + // Suggestions actually have never been meant to influence loading order. + // We misuse this currently, as there is no other way to influence the loading order + // for not-required packages (soft-dependency). + // When considering suggestions for the loading order, we might create a cyclic dependency + // if the suggested package already has a real dependency on this package, so the suggestion + // has do be dropped in this case and must *not* be taken into account for loading order evaluation. + $dependencies[$packageKey]['after-resilient'][] = $suggestedPackageKey; + } + } + } + } + return $dependencies; + } + + /** + * Adds all root packages of current dependency graph as dependency to all extensions + * + * This ensures that the framework extensions (aka sysext) are + * always loaded first, before any other external extension. + * + * @param array $packageStateConfiguration + * @param array $rootPackageKeys + * @return array + */ + protected function addDependencyToFrameworkToAllExtensions(array $packageStateConfiguration, array $rootPackageKeys) + { + $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration); + $extensionPackageKeys = array_diff(array_keys($packageStateConfiguration), $frameworkPackageKeys); + foreach ($extensionPackageKeys as $packageKey) { + // Remove framework packages from list + $packageKeysWithoutFramework = array_diff( + $packageStateConfiguration[$packageKey]['dependencies'], + $frameworkPackageKeys + ); + // The order of the array_merge is crucial here, + // we want the framework first + $packageStateConfiguration[$packageKey]['dependencies'] = array_merge( + $rootPackageKeys, + $packageKeysWithoutFramework + ); + } + return $packageStateConfiguration; + } + + /** + * Builds the dependency graph for all packages + * + * This method also introduces dependencies among the dependencies + * to ensure the loading order is exactly as specified in the list. + * + * @param array $packageStateConfiguration + * @return array + */ + protected function buildDependencyGraph(array $packageStateConfiguration) + { + $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration); + $frameworkPackagesDependencyGraph = $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $frameworkPackageKeys)); + $packageStateConfiguration = $this->addDependencyToFrameworkToAllExtensions($packageStateConfiguration, $this->dependencyOrderingService->findRootIds($frameworkPackagesDependencyGraph)); + + $packageKeys = array_keys($packageStateConfiguration); + return $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $packageKeys)); + } + + /** + * @param array $packageStateConfiguration + * @return array + */ + protected function findFrameworkPackages(array $packageStateConfiguration) + { + $frameworkPackageKeys = []; + foreach ($packageStateConfiguration as $packageKey => $packageConfiguration) { + $package = $this->getPackage($packageKey); + if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') { + $frameworkPackageKeys[] = $packageKey; + } + } + + return $frameworkPackageKeys; + } } diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84109-DeprecateDependencyResolver.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84109-DeprecateDependencyResolver.rst new file mode 100644 index 000000000000..4002458223c0 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84109-DeprecateDependencyResolver.rst @@ -0,0 +1,43 @@ +.. include:: ../../Includes.txt + +================================================== +Deprecation: #84109 - Deprecate DependencyResolver +================================================== + +See :issue:`84109` + +Description +=========== + +The class :php:`\TYPO3\CMS\Core\Package\DependencyResolver` has been marked as +deprecated as the code as been merged into :php:`\TYPO3\CMS\Core\Package\PackageManager`. +Additionally the :php:`\TYPO3\CMS\Core\Package\PackageManager` method +:php:`injectDependencyResolver` has been deprecated and the +:php:`\TYPO3\CMS\Core\Package\PackageManager` triggers a deprecation log entry +when the :php:`\TYPO3\CMS\Core\Service\DependencyOrderingService` is not injected +through the constructor. + +Impact +====== + +Installations that use :php:`\TYPO3\CMS\Core\Package\DependencyResolver` or create +an own :php:`\TYPO3\CMS\Core\Package\PackageManager` instance will trigger a +deprecation log entry. + + +Affected Installations +====================== + +All installations that use custom extensions that use the +:php:`\TYPO3\CMS\Core\Package\DependencyResolver` class or create +an own :php:`\TYPO3\CMS\Core\Package\PackageManager` instance. + + +Migration +========= + +Use :php:`\TYPO3\CMS\Core\Service\DependencyOrderingService` to manually sort packages. +Pass :php:`\TYPO3\CMS\Core\Service\DependencyOrderingService` to the +:php:`\TYPO3\CMS\Core\Package\PackageManager` constructor if a new instance is created. + +.. index:: PHP-API, FullyScanned diff --git a/typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php b/typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php index 822dd4e84389..850d2ceaed6e 100644 --- a/typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php +++ b/typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php @@ -14,11 +14,11 @@ use org\bovigo\vfs\vfsStream; use TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend; use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; -use TYPO3\CMS\Core\Package\DependencyResolver; use TYPO3\CMS\Core\Package\Exception\ProtectedPackageKeyException; use TYPO3\CMS\Core\Package\Exception\UnknownPackageException; use TYPO3\CMS\Core\Package\Package; use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\Service\DependencyOrderingService; /** * Testcase for the default package manager @@ -55,7 +55,11 @@ protected function setUp() $mockCache->expects($this->any())->method('set')->will($this->returnValue(true)); $mockCache->expects($this->any())->method('getBackend')->will($this->returnValue($mockCacheBackend)); $mockCacheBackend->expects($this->any())->method('getCacheDirectory')->will($this->returnValue('vfs://Test/Cache')); - $this->packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates', 'sortActivePackagesByDependencies', 'registerTransientClassLoadingInformationForPackage']); + $this->packageManager = $this->getAccessibleMock( + PackageManager::class, + ['sortAndSavePackageStates', 'sortActivePackagesByDependencies', 'registerTransientClassLoadingInformationForPackage'], + [new DependencyOrderingService] + ); mkdir('vfs://Test/Packages/Application', 0700, true); mkdir('vfs://Test/Configuration'); @@ -129,7 +133,7 @@ public function scanAvailablePackagesTraversesThePackagesDirectoryAndRegistersPa file_put_contents($packagePath . 'composer.json', '{"name": "' . $packageKey . '", "type": "typo3-test"}'); } - $packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates']); + $packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates'], [new DependencyOrderingService]); $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/'); $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php'); @@ -164,20 +168,11 @@ public function scanAvailablePackagesKeepsExistingPackageConfiguration() } /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $packageManager */ - $packageManager = $this->getAccessibleMock(PackageManager::class, ['dummy']); + $packageManager = $this->getAccessibleMock(PackageManager::class, ['dummy'], [new DependencyOrderingService]); $packageManager->_set('packagesBasePaths', $packagePaths); $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/'); $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php'); - /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject $dependencyResolver */ - $dependencyResolver = $this->createMock(DependencyResolver::class); - $dependencyResolver - ->expects($this->any()) - ->method('sortPackageStatesConfigurationByDependency') - ->willReturnCallback('array_keys'); - - $packageManager->injectDependencyResolver($dependencyResolver); - $packageKey = $expectedPackageKeys[0]; $packageManager->_set('packageStatesConfiguration', [ 'packages' => [ @@ -216,20 +211,11 @@ public function packageStatesConfigurationContainsRelativePaths() } /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $packageManager */ - $packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates']); + $packageManager = $this->getAccessibleMock(PackageManager::class, ['sortAndSavePackageStates', 'registerTransientClassLoadingInformationForPackage'], [new DependencyOrderingService]); $packageManager->_set('packagesBasePaths', $packagePaths); $packageManager->_set('packagesBasePath', 'vfs://Test/Packages/'); $packageManager->_set('packageStatesPathAndFilename', 'vfs://Test/Configuration/PackageStates.php'); - /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject $dependencyResolver */ - $dependencyResolver = $this->createMock(DependencyResolver::class); - $dependencyResolver - ->expects($this->any()) - ->method('sortPackageStatesConfigurationByDependency') - ->willReturnCallback('array_keys'); - - $packageManager->injectDependencyResolver($dependencyResolver); - $packageManager->_set('packages', []); $packageManager->_call('scanAvailablePackages'); @@ -378,10 +364,420 @@ public function getPackageKeyFromComposerNameIgnoresCaseDifferences($composerNam 'imagine/imagine' => 'imagine.Imagine' ]; - $packageManager = $this->getAccessibleMock(PackageManager::class, ['resolvePackageDependencies']); + $packageManager = $this->getAccessibleMock(PackageManager::class, ['resolvePackageDependencies'], [new DependencyOrderingService]); $packageManager->_set('packageStatesConfiguration', $packageStatesConfiguration); $packageManager->_set('composerNameToPackageKeyMap', $composerNameToPackageKeyMap); $this->assertEquals($packageKey, $packageManager->_call('getPackageKeyFromComposerName', $composerName)); } + + /** + * @return array + */ + public function buildDependencyGraphBuildsCorrectGraphDataProvider() + { + return [ + 'TYPO3 Flow Packages' => [ + [ + 'TYPO3.Flow' => [ + 'dependencies' => ['Symfony.Component.Yaml', 'Doctrine.Common', 'Doctrine.DBAL', 'Doctrine.ORM'] + ], + 'Doctrine.ORM' => [ + 'dependencies' => ['Doctrine.Common', 'Doctrine.DBAL'] + ], + 'Doctrine.Common' => [ + 'dependencies' => [] + ], + 'Doctrine.DBAL' => [ + 'dependencies' => ['Doctrine.Common'] + ], + 'Symfony.Component.Yaml' => [ + 'dependencies' => [] + ], + ], + [ + 'Doctrine.Common' + ], + [ + 'TYPO3.Flow' => [ + 'TYPO3.Flow' => false, + 'Doctrine.ORM' => true, + 'Doctrine.Common' => true, + 'Doctrine.DBAL' => true, + 'Symfony.Component.Yaml' => true, + ], + 'Doctrine.ORM' => [ + 'TYPO3.Flow' => false, + 'Doctrine.ORM' => false, + 'Doctrine.Common' => true, + 'Doctrine.DBAL' => true, + 'Symfony.Component.Yaml' => false, + ], + 'Doctrine.Common' => [ + 'TYPO3.Flow' => false, + 'Doctrine.ORM' => false, + 'Doctrine.Common' => false, + 'Doctrine.DBAL' => false, + 'Symfony.Component.Yaml' => false, + ], + 'Doctrine.DBAL' => [ + 'TYPO3.Flow' => false, + 'Doctrine.ORM' => false, + 'Doctrine.Common' => true, + 'Doctrine.DBAL' => false, + 'Symfony.Component.Yaml' => false, + ], + 'Symfony.Component.Yaml' => [ + 'TYPO3.Flow' => false, + 'Doctrine.ORM' => false, + 'Doctrine.Common' => true, + 'Doctrine.DBAL' => false, + 'Symfony.Component.Yaml' => false, + ], + ], + ], + 'TYPO3 CMS Extensions' => [ + [ + 'core' => [ + 'dependencies' => [], + ], + 'setup' => [ + 'dependencies' => ['core'], + ], + 'openid' => [ + 'dependencies' => ['core', 'setup'] + ], + 'news' => [ + 'dependencies' => ['extbase'], + ], + 'extbase' => [ + 'dependencies' => ['core'], + ], + 'pt_extbase' => [ + 'dependencies' => ['extbase'], + ], + 'foo' => [ + 'dependencies' => [], + ], + ], + [ + 'core', + 'setup', + 'openid', + 'extbase' + ], + [ + 'core' => [ + 'core' => false, + 'setup' => false, + 'openid' => false, + 'news' => false, + 'extbase' => false, + 'pt_extbase' => false, + 'foo' => false + ], + 'setup' => [ + 'core' => true, + 'setup' => false, + 'openid' => false, + 'news' => false, + 'extbase' => false, + 'pt_extbase' => false, + 'foo' => false + ], + 'openid' => [ + 'core' => true, + 'setup' => true, + 'openid' => false, + 'news' => false, + 'extbase' => false, + 'pt_extbase' => false, + 'foo' => false + ], + 'news' => [ + 'core' => false, + 'setup' => false, + 'openid' => true, + 'news' => false, + 'extbase' => true, + 'pt_extbase' => false, + 'foo' => false + ], + 'extbase' => [ + 'core' => true, + 'setup' => false, + 'openid' => false, + 'news' => false, + 'extbase' => false, + 'pt_extbase' => false, + 'foo' => false + ], + 'pt_extbase' => [ + 'core' => false, + 'setup' => false, + 'openid' => true, + 'news' => false, + 'extbase' => true, + 'pt_extbase' => false, + 'foo' => false + ], + 'foo' => [ + 'core' => false, + 'setup' => false, + 'openid' => true, + 'news' => false, + 'extbase' => true, + 'pt_extbase' => false, + 'foo' => false + ], + ], + ], + 'Dummy Packages' => [ + [ + 'A' => [ + 'dependencies' => ['B', 'D', 'C'], + ], + 'B' => [ + 'dependencies' => [] + ], + 'C' => [ + 'dependencies' => ['E'] + ], + 'D' => [ + 'dependencies' => ['E'], + ], + 'E' => [ + 'dependencies' => [], + ], + 'F' => [ + 'dependencies' => [], + ], + ], + [ + 'B', + 'C', + 'E' + ], + [ + 'A' => [ + 'A' => false, + 'B' => true, + 'C' => true, + 'D' => true, + 'E' => false, + 'F' => false, + ], + 'B' => [ + 'A' => false, + 'B' => false, + 'C' => false, + 'D' => false, + 'E' => false, + 'F' => false, + ], + 'C' => [ + 'A' => false, + 'B' => false, + 'C' => false, + 'D' => false, + 'E' => true, + 'F' => false, + ], + 'D' => [ + 'A' => false, + 'B' => true, + 'C' => true, + 'D' => false, + 'E' => false, + 'F' => false, + ], + 'E' => [ + 'A' => false, + 'B' => false, + 'C' => false, + 'D' => false, + 'E' => false, + 'F' => false, + ], + 'F' => [ + 'A' => false, + 'B' => true, + 'C' => true, + 'D' => false, + 'E' => false, + 'F' => false, + ], + ], + ], + ]; + } + + /** + * @test + * @param array $unsortedPackageStatesConfiguration + * @param array $frameworkPackageKeys + * @param array $expectedGraph + * @dataProvider buildDependencyGraphBuildsCorrectGraphDataProvider + */ + public function buildDependencyGraphBuildsCorrectGraph(array $unsortedPackageStatesConfiguration, array $frameworkPackageKeys, array $expectedGraph) + { + $packageManager = $this->getAccessibleMock(PackageManager::class, ['findFrameworkPackages'], [new DependencyOrderingService]); + $packageManager->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys); + + $dependencyGraph = $packageManager->_call('buildDependencyGraph', $unsortedPackageStatesConfiguration); + + $this->assertEquals($expectedGraph, $dependencyGraph); + } + + /** + * @return array + */ + public function packageSortingDataProvider() + { + return [ + 'TYPO3 Flow Packages' => [ + [ + 'TYPO3.Flow' => [ + 'dependencies' => ['Symfony.Component.Yaml', 'Doctrine.Common', 'Doctrine.DBAL', 'Doctrine.ORM'] + ], + 'Doctrine.ORM' => [ + 'dependencies' => ['Doctrine.Common', 'Doctrine.DBAL'] + ], + 'Doctrine.Common' => [ + 'dependencies' => [] + ], + 'Doctrine.DBAL' => [ + 'dependencies' => ['Doctrine.Common'] + ], + 'Symfony.Component.Yaml' => [ + 'dependencies' => [] + ], + ], + [ + 'Doctrine.Common' + ], + [ + 'Doctrine.Common', + 'Doctrine.DBAL', + 'Doctrine.ORM', + 'Symfony.Component.Yaml', + 'TYPO3.Flow', + ], + ], + 'TYPO3 CMS Extensions' => [ + [ + 'core' => [ + 'dependencies' => [], + ], + 'setup' => [ + 'dependencies' => ['core'], + ], + 'openid' => [ + 'dependencies' => ['core', 'setup'] + ], + 'news' => [ + 'dependencies' => ['extbase'], + ], + 'extbase' => [ + 'dependencies' => ['core'], + ], + 'pt_extbase' => [ + 'dependencies' => ['extbase'], + ], + 'foo' => [ + 'dependencies' => [], + ], + ], + [ + 'core', + 'setup', + 'openid', + 'extbase' + ], + [ + 'core', + 'setup', + 'openid', + 'extbase', + 'foo', + 'news', + 'pt_extbase', + ], + ], + 'Dummy Packages' => [ + [ + 'A' => [ + 'dependencies' => ['B', 'D', 'C'], + ], + 'B' => [ + 'dependencies' => [] + ], + 'C' => [ + 'dependencies' => ['E'] + ], + 'D' => [ + 'dependencies' => ['E'], + ], + 'E' => [ + 'dependencies' => [], + ], + 'F' => [ + 'dependencies' => [], + ], + ], + [ + 'B', + 'C', + 'E' + ], + [ + 'E', + 'C', + 'B', + 'D', + 'A', + 'F', + ], + ], + ]; + } + + /** + * @test + * @dataProvider packageSortingDataProvider + * @param array $unsortedPackageStatesConfiguration + * @param array $frameworkPackageKeys + */ + public function sortPackageStatesConfigurationByDependencyMakesSureThatDependantPackagesAreStandingBeforeAPackageInTheInternalPackagesAndPackagesConfigurationArrays($unsortedPackageStatesConfiguration, $frameworkPackageKeys, $expectedSortedPackageKeys) + { + $packageManager = $this->getAccessibleMock(PackageManager::class, ['findFrameworkPackages'], [new DependencyOrderingService]); + $packageManager->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys); + + $sortedPackageKeys = $packageManager->_call('sortPackageStatesConfigurationByDependency', $unsortedPackageStatesConfiguration); + + $this->assertEquals($expectedSortedPackageKeys, $sortedPackageKeys, 'The package states configurations have not been ordered according to their dependencies!'); + } + + /** + * @test + */ + public function sortPackageStatesConfigurationByDependencyThrowsExceptionWhenCycleDetected() + { + $unsortedPackageStatesConfiguration = [ + 'A' => [ + 'dependencies' => ['B'], + ], + 'B' => [ + 'dependencies' => ['A'] + ], + ]; + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionCode(1381960494); + + $packageManager = $this->getAccessibleMock(PackageManager::class, ['findFrameworkPackages'], [new DependencyOrderingService]); + $packageManager->expects($this->any())->method('findFrameworkPackages')->willReturn([]); + + $packageManager->_call('sortPackageStatesConfigurationByDependency', $unsortedPackageStatesConfiguration); + } } diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php b/typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php index 0bf388e84075..c7dd0aae5c09 100644 --- a/typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php +++ b/typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php @@ -136,6 +136,7 @@ public function extensionStaticsAreProcessedIfExplicitlyRequested() $mockPackageManager = $this->getMockBuilder(\TYPO3\CMS\Core\Package\PackageManager::class) ->setMethods(['isPackageActive', 'getPackage']) + ->disableOriginalConstructor() ->getMock(); $mockPackageManager->expects($this->any())->method('isPackageActive')->will($this->returnValue(true)); $mockPackageManager->expects($this->any())->method('getPackage')->will($this->returnValue($mockPackage)); diff --git a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php index da9bd6d50eae..27ff22c38b3d 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php @@ -78,6 +78,7 @@ protected function createMockPackageManagerWithMockPackage($packageKey, $package ->getMock(); $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive', 'getPackage', 'getActivePackages']) + ->disableOriginalConstructor() ->getMock(); $package->expects($this->any()) ->method('getPackagePath') @@ -127,6 +128,7 @@ public function extPathThrowsExceptionIfExtensionIsNotLoaded() /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */ $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive']) + ->disableOriginalConstructor() ->getMock(); $packageManager->expects($this->once()) ->method('isPackageActive') @@ -148,6 +150,7 @@ public function extPathAppendsScriptNameToPath() /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */ $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive', 'getPackage']) + ->disableOriginalConstructor() ->getMock(); $package->expects($this->once()) ->method('getPackagePath') @@ -1750,6 +1753,7 @@ public function unloadExtensionThrowsExceptionIfExtensionIsNotLoaded() /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */ $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive']) + ->disableOriginalConstructor() ->getMock(); $packageManager->expects($this->once()) ->method('isPackageActive') @@ -1768,6 +1772,7 @@ public function unloadExtensionCallsPackageManagerToDeactivatePackage() /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */ $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive', 'deactivatePackage']) + ->disableOriginalConstructor() ->getMock(); $packageManager->expects($this->any()) ->method('isPackageActive') diff --git a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php index 05cce1215e25..b5f3587799ff 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php @@ -4074,6 +4074,7 @@ public function getFileAbsFileNameReturnsCorrectValues($path, $expected) /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */ $packageManager = $this->getMockBuilder(PackageManager::class) ->setMethods(['isPackageActive', 'getPackage']) + ->disableOriginalConstructor() ->getMock(); $package->expects($this->any()) ->method('getPackagePath') diff --git a/typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Package/DependencyResolverTest.php similarity index 97% rename from typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php rename to typo3/sysext/core/Tests/UnitDeprecated/Package/DependencyResolverTest.php index 4e0fb1839f81..977247b2807f 100644 --- a/typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php +++ b/typo3/sysext/core/Tests/UnitDeprecated/Package/DependencyResolverTest.php @@ -172,7 +172,10 @@ public function buildDependencyGraphBuildsCorrectGraphDataProvider() ], ], [ - 'core', 'setup', 'openid', 'extbase' + 'core', + 'setup', + 'openid', + 'extbase' ], [ 'core' => [ @@ -262,7 +265,9 @@ public function buildDependencyGraphBuildsCorrectGraphDataProvider() ], ], [ - 'B', 'C', 'E' + 'B', + 'C', + 'E' ], [ 'A' => [ @@ -378,7 +383,10 @@ public function packageSortingDataProvider() ], ], [ - 'core', 'setup', 'openid', 'extbase' + 'core', + 'setup', + 'openid', + 'extbase' ], [ 'core', @@ -412,7 +420,9 @@ public function packageSortingDataProvider() ], ], [ - 'B', 'C', 'E' + 'B', + 'C', + 'E' ], [ 'E', diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php index 798648e00e7d..631a52cd0684 100644 --- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php +++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php @@ -36,7 +36,9 @@ protected function setUp() $this->subject = $this->getMockBuilder(\TYPO3\CMS\Extensionmanager\Utility\ListUtility::class) ->setMethods(['emitPackagesMayHaveChangedSignal']) ->getMock(); - $packageManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Package\PackageManager::class)->getMock(); + $packageManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Package\PackageManager::class) + ->disableOriginalConstructor() + ->getMock(); $packageManagerMock ->expects($this->any()) ->method('getActivePackages') diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php index 39fcca27ea5c..173eb5651629 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php @@ -649,4 +649,9 @@ 'Deprecation-83511-DeprecateAbstractValidatorTestcase.rst', ], ], + 'TYPO3\CMS\Core\Package\DependencyResolver' => [ + 'restFiles' => [ + 'Deprecation-84109-DeprecateDependencyResolver.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php index ca3d680fe536..7eb997fb4bb7 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php @@ -7,4 +7,11 @@ 'Breaking-80700-DeprecatedFunctionalityRemoved.rst', ], ], + 'TYPO3\CMS\Core\Package\PackageManager->__construct' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Deprecation-84109-DeprecateDependencyResolver.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index 0cbc55ac4c49..fffaed394762 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -1786,5 +1786,12 @@ 'restFiles' => [ 'Deprecation-84145-DeprecateExt_isLinkable.rst' ], - ] + ], + 'TYPO3\CMS\Core\Package\PackageManager->injectDependencyResolver' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Deprecation-84109-DeprecateDependencyResolver.rst', + ], + ], ];