[TASK] Only show active packages in PackageStates.php
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Package / DependencyResolver.php
1 <?php
2 namespace TYPO3\CMS\Core\Package;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Core\Bootstrap;
18 use TYPO3\CMS\Core\Service\DependencyOrderingService;
19
20 /**
21 * This class takes care about dependencies between packages.
22 * It provides functionality to resolve dependencies and to determine
23 * the crucial loading order of the packages.
24 */
25 class DependencyResolver
26 {
27 /**
28 * @var DependencyOrderingService
29 */
30 protected $dependencyOrderingService;
31
32 /**
33 * @param DependencyOrderingService $dependencyOrderingService
34 */
35 public function injectDependencyOrderingService(DependencyOrderingService $dependencyOrderingService)
36 {
37 $this->dependencyOrderingService = $dependencyOrderingService;
38 }
39
40 /**
41 * @param array $packageStatesConfiguration
42 * @return array Returns the packageStatesConfiguration sorted by dependencies
43 * @throws \UnexpectedValueException
44 */
45 public function sortPackageStatesConfigurationByDependency(array $packageStatesConfiguration)
46 {
47 return $this->dependencyOrderingService->calculateOrder($this->buildDependencyGraph($packageStatesConfiguration));
48 }
49
50 /**
51 * Convert the package configuration into a dependency definition
52 *
53 * This converts "dependencies" and "suggestions" to "after" syntax for the usage in DependencyOrderingService
54 *
55 * @param array $packageStatesConfiguration
56 * @param array $packageKeys
57 * @return array
58 * @throws \UnexpectedValueException
59 */
60 protected function convertConfigurationForGraph(array $packageStatesConfiguration, array $packageKeys)
61 {
62 $dependencies = [];
63 foreach ($packageKeys as $packageKey) {
64 if (!isset($packageStatesConfiguration[$packageKey]['dependencies']) && !isset($packageStatesConfiguration[$packageKey]['suggestions'])) {
65 continue;
66 }
67 $dependencies[$packageKey] = [
68 'after' => []
69 ];
70 if (isset($packageStatesConfiguration[$packageKey]['dependencies'])) {
71 foreach ($packageStatesConfiguration[$packageKey]['dependencies'] as $dependentPackageKey) {
72 if (!in_array($dependentPackageKey, $packageKeys, true)) {
73 throw new \UnexpectedValueException(
74 'The package "' . $packageKey . '" depends on "'
75 . $dependentPackageKey . '" which is not present in the system.',
76 1382276561);
77 }
78 $dependencies[$packageKey]['after'][] = $dependentPackageKey;
79 }
80 }
81 if (isset($packageStatesConfiguration[$packageKey]['suggestions'])) {
82 foreach ($packageStatesConfiguration[$packageKey]['suggestions'] as $suggestedPackageKey) {
83 // skip suggestions on not existing packages
84 if (in_array($suggestedPackageKey, $packageKeys, true)) {
85 // Suggestions actually have never been meant to influence loading order.
86 // We misuse this currently, as there is no other way to influence the loading order
87 // for not-required packages (soft-dependency).
88 // When considering suggestions for the loading order, we might create a cyclic dependency
89 // if the suggested package already has a real dependency on this package, so the suggestion
90 // has do be dropped in this case and must *not* be taken into account for loading order evaluation.
91 $dependencies[$packageKey]['after-resilient'][] = $suggestedPackageKey;
92 }
93 }
94 }
95 }
96 return $dependencies;
97 }
98
99 /**
100 * Adds all root packages of current dependency graph as dependency to all extensions
101 *
102 * This ensures that the framework extensions (aka sysext) are
103 * always loaded first, before any other external extension.
104 *
105 * @param array $packageStateConfiguration
106 * @param array $rootPackageKeys
107 * @return array
108 */
109 protected function addDependencyToFrameworkToAllExtensions(array $packageStateConfiguration, array $rootPackageKeys)
110 {
111 $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration);
112 $extensionPackageKeys = array_diff(array_keys($packageStateConfiguration), $frameworkPackageKeys);
113 foreach ($extensionPackageKeys as $packageKey) {
114 // Remove framework packages from list
115 $packageKeysWithoutFramework = array_diff(
116 $packageStateConfiguration[$packageKey]['dependencies'],
117 $frameworkPackageKeys
118 );
119 // The order of the array_merge is crucial here,
120 // we want the framework first
121 $packageStateConfiguration[$packageKey]['dependencies'] = array_merge(
122 $rootPackageKeys, $packageKeysWithoutFramework
123 );
124 }
125 return $packageStateConfiguration;
126 }
127
128 /**
129 * Builds the dependency graph for all packages
130 *
131 * This method also introduces dependencies among the dependencies
132 * to ensure the loading order is exactly as specified in the list.
133 *
134 * @param array $packageStateConfiguration
135 * @return array
136 */
137 protected function buildDependencyGraph(array $packageStateConfiguration)
138 {
139 $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration);
140 $frameworkPackagesDependencyGraph = $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $frameworkPackageKeys));
141 $packageStateConfiguration = $this->addDependencyToFrameworkToAllExtensions($packageStateConfiguration, $this->dependencyOrderingService->findRootIds($frameworkPackagesDependencyGraph));
142
143 $packageKeys = array_keys($packageStateConfiguration);
144 return $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $packageKeys));
145 }
146
147 /**
148 * @param array $packageStateConfiguration
149 * @return array
150 * @throws \TYPO3\CMS\Core\Exception
151 */
152 protected function findFrameworkPackages(array $packageStateConfiguration)
153 {
154 $frameworkPackageKeys = array();
155 /** @var PackageManager $packageManager */
156 $packageManager = Bootstrap::getInstance()->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class);
157 foreach ($packageStateConfiguration as $packageKey => $packageConfiguration) {
158 /** @var Package $package */
159 $package = $packageManager->getPackage($packageKey);
160 if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') {
161 $frameworkPackageKeys[] = $packageKey;
162 }
163 }
164
165 return $frameworkPackageKeys;
166 }
167 }