[TASK] Merge the package DependencyResolver into the PackageManager 77/55977/16
authorBenjamin Franzke <bfr@qbus.de>
Thu, 1 Mar 2018 18:19:21 +0000 (19:19 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 7 Mar 2018 22:45:31 +0000 (23:45 +0100)
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 <mathias.schreiber@typo3.com>
Tested-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
14 files changed:
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Package/DependencyResolver.php
typo3/sysext/core/Classes/Package/PackageManager.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-84109-DeprecateDependencyResolver.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php [deleted file]
typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php
typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php
typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
typo3/sysext/core/Tests/UnitDeprecated/Package/DependencyResolverTest.php [new file with mode: 0644]
typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentRequiredMatcher.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php

index 3611cbc..837fe36 100644 (file)
@@ -292,15 +292,13 @@ class Bootstrap
      */
     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;
     }
index 16006a8..91d12e6 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * 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 @@ class DependencyResolver
      */
     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 @@ class DependencyResolver
     /**
      * @param array $packageStateConfiguration
      * @return array
-     * @throws \TYPO3\CMS\Core\Exception
      */
     protected function findFrameworkPackages(array $packageStateConfiguration)
     {
index 488910e..f63139f 100644 (file)
@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 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 @@ use TYPO3\CMS\Core\Utility\StringUtility;
 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 @@ class PackageManager implements \TYPO3\CMS\Core\SingletonInterface
 
     /**
      * @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 @@ class PackageManager implements \TYPO3\CMS\Core\SingletonInterface
 
         // 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 @@ class PackageManager implements \TYPO3\CMS\Core\SingletonInterface
     {
         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 (file)
index 0000000..4002458
--- /dev/null
@@ -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/DependencyResolverTest.php b/typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php
deleted file mode 100644 (file)
index 4e0fb18..0000000
+++ /dev/null
@@ -1,428 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests\Unit\Package;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Package\DependencyResolver;
-use TYPO3\CMS\Core\Service\DependencyOrderingService;
-
-/**
- * Test case
- */
-class DependencyResolverTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
-{
-    /**
-     * @test
-     * @param array $unsortedPackageStatesConfiguration
-     * @param array $frameworkPackageKeys
-     * @param array $expectedGraph
-     * @dataProvider buildDependencyGraphBuildsCorrectGraphDataProvider
-     */
-    public function buildDependencyGraphBuildsCorrectGraph(array $unsortedPackageStatesConfiguration, array $frameworkPackageKeys, array $expectedGraph)
-    {
-        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
-        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
-        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
-        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys);
-        $dependencyGraph = $dependencyResolver->_call('buildDependencyGraph', $unsortedPackageStatesConfiguration);
-
-        $this->assertEquals($expectedGraph, $dependencyGraph);
-    }
-
-    /**
-     * @test
-     * @dataProvider packageSortingDataProvider
-     * @param array $unsortedPackageStatesConfiguration
-     * @param array $frameworkPackageKeys
-     * @param array $expectedSortedPackageStatesConfiguration
-     */
-    public function sortPackageStatesConfigurationByDependencyMakesSureThatDependantPackagesAreStandingBeforeAPackageInTheInternalPackagesAndPackagesConfigurationArrays($unsortedPackageStatesConfiguration, $frameworkPackageKeys, $expectedSortedPackageKeys)
-    {
-        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
-        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
-        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
-        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys);
-        $sortedPackageKeys = $dependencyResolver->_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);
-
-        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
-        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
-        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
-        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn([]);
-        $dependencyResolver->_call('sortPackageStatesConfigurationByDependency', $unsortedPackageStatesConfiguration);
-    }
-
-    /**
-     * @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,
-                    ],
-                ],
-            ],
-        ];
-    }
-
-    /**
-     * @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',
-                ],
-            ],
-        ];
-    }
-}
index 822dd4e..850d2ce 100644 (file)
@@ -14,11 +14,11 @@ namespace TYPO3\CMS\Core\Tests\Unit\Package;
 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 @@ class PackageManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $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 @@ class PackageManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
             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 @@ class PackageManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         }
 
         /** @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 @@ class PackageManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         }
 
         /** @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 @@ class PackageManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
             '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);
+    }
 }
index 0bf388e..c7dd0aa 100644 (file)
@@ -136,6 +136,7 @@ class TemplateServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
 
         $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));
index da9bd6d..27ff22c 100644 (file)
@@ -78,6 +78,7 @@ class ExtensionManagementUtilityTest extends \TYPO3\TestingFramework\Core\Unit\U
                 ->getMock();
         $packageManager = $this->getMockBuilder(PackageManager::class)
             ->setMethods(['isPackageActive', 'getPackage', 'getActivePackages'])
+            ->disableOriginalConstructor()
             ->getMock();
         $package->expects($this->any())
                 ->method('getPackagePath')
@@ -127,6 +128,7 @@ class ExtensionManagementUtilityTest extends \TYPO3\TestingFramework\Core\Unit\U
         /** @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 @@ class ExtensionManagementUtilityTest extends \TYPO3\TestingFramework\Core\Unit\U
         /** @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 @@ class ExtensionManagementUtilityTest extends \TYPO3\TestingFramework\Core\Unit\U
         /** @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 @@ class ExtensionManagementUtilityTest extends \TYPO3\TestingFramework\Core\Unit\U
         /** @var PackageManager|\PHPUnit_Framework_MockObject_MockObject $packageManager */
         $packageManager = $this->getMockBuilder(PackageManager::class)
             ->setMethods(['isPackageActive', 'deactivatePackage'])
+            ->disableOriginalConstructor()
             ->getMock();
         $packageManager->expects($this->any())
             ->method('isPackageActive')
index 05cce12..b5f3587 100644 (file)
@@ -4074,6 +4074,7 @@ class GeneralUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         /** @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/UnitDeprecated/Package/DependencyResolverTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Package/DependencyResolverTest.php
new file mode 100644 (file)
index 0000000..977247b
--- /dev/null
@@ -0,0 +1,438 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\Package;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Package\DependencyResolver;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
+
+/**
+ * Test case
+ */
+class DependencyResolverTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
+{
+    /**
+     * @test
+     * @param array $unsortedPackageStatesConfiguration
+     * @param array $frameworkPackageKeys
+     * @param array $expectedGraph
+     * @dataProvider buildDependencyGraphBuildsCorrectGraphDataProvider
+     */
+    public function buildDependencyGraphBuildsCorrectGraph(array $unsortedPackageStatesConfiguration, array $frameworkPackageKeys, array $expectedGraph)
+    {
+        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
+        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
+        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
+        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys);
+        $dependencyGraph = $dependencyResolver->_call('buildDependencyGraph', $unsortedPackageStatesConfiguration);
+
+        $this->assertEquals($expectedGraph, $dependencyGraph);
+    }
+
+    /**
+     * @test
+     * @dataProvider packageSortingDataProvider
+     * @param array $unsortedPackageStatesConfiguration
+     * @param array $frameworkPackageKeys
+     * @param array $expectedSortedPackageStatesConfiguration
+     */
+    public function sortPackageStatesConfigurationByDependencyMakesSureThatDependantPackagesAreStandingBeforeAPackageInTheInternalPackagesAndPackagesConfigurationArrays($unsortedPackageStatesConfiguration, $frameworkPackageKeys, $expectedSortedPackageKeys)
+    {
+        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
+        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
+        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
+        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn($frameworkPackageKeys);
+        $sortedPackageKeys = $dependencyResolver->_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);
+
+        /** @var DependencyResolver|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $dependencyResolver */
+        $dependencyResolver = $this->getAccessibleMock(DependencyResolver::class, ['findFrameworkPackages']);
+        $dependencyResolver->injectDependencyOrderingService(new DependencyOrderingService());
+        $dependencyResolver->expects($this->any())->method('findFrameworkPackages')->willReturn([]);
+        $dependencyResolver->_call('sortPackageStatesConfigurationByDependency', $unsortedPackageStatesConfiguration);
+    }
+
+    /**
+     * @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,
+                    ],
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * @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',
+                ],
+            ],
+        ];
+    }
+}
index 798648e..631a52c 100644 (file)
@@ -36,7 +36,9 @@ class ListUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $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')
index 39fcca2..173eb56 100644 (file)
@@ -649,4 +649,9 @@ return [
             'Deprecation-83511-DeprecateAbstractValidatorTestcase.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Package\DependencyResolver' => [
+        'restFiles' => [
+            'Deprecation-84109-DeprecateDependencyResolver.rst',
+        ],
+    ],
 ];
index ca3d680..7eb997f 100644 (file)
@@ -7,4 +7,11 @@ return [
             'Breaking-80700-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Package\PackageManager->__construct' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-84109-DeprecateDependencyResolver.rst',
+        ],
+    ],
 ];
index 0cbc55a..fffaed3 100644 (file)
@@ -1786,5 +1786,12 @@ return [
         'restFiles' => [
             'Deprecation-84145-DeprecateExt_isLinkable.rst'
         ],
-    ]
+    ],
+    'TYPO3\CMS\Core\Package\PackageManager->injectDependencyResolver' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-84109-DeprecateDependencyResolver.rst',
+        ],
+    ],
 ];