0c7e25990f587d700f58dfa2dea60de9f25c17c3
[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\Service\DependencyOrderingService;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
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 * @deprecated since TYPO3 v9.2, will be removed in TYPO3 v10.0
25 */
26 class DependencyResolver
27 {
28 /**
29 * @var DependencyOrderingService
30 */
31 protected $dependencyOrderingService;
32
33 /**
34 * @param DependencyOrderingService $dependencyOrderingService
35 */
36 public function injectDependencyOrderingService(DependencyOrderingService $dependencyOrderingService)
37 {
38 $this->dependencyOrderingService = $dependencyOrderingService;
39 }
40
41 /**
42 * @param array $packageStatesConfiguration
43 * @return array Returns the packageStatesConfiguration sorted by dependencies
44 * @throws \UnexpectedValueException
45 */
46 public function sortPackageStatesConfigurationByDependency(array $packageStatesConfiguration)
47 {
48 trigger_error(self::class . ' will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
49 return $this->dependencyOrderingService->calculateOrder($this->buildDependencyGraph($packageStatesConfiguration));
50 }
51
52 /**
53 * Convert the package configuration into a dependency definition
54 *
55 * This converts "dependencies" and "suggestions" to "after" syntax for the usage in DependencyOrderingService
56 *
57 * @param array $packageStatesConfiguration
58 * @param array $packageKeys
59 * @return array
60 * @throws \UnexpectedValueException
61 */
62 protected function convertConfigurationForGraph(array $packageStatesConfiguration, array $packageKeys)
63 {
64 $dependencies = [];
65 foreach ($packageKeys as $packageKey) {
66 if (!isset($packageStatesConfiguration[$packageKey]['dependencies']) && !isset($packageStatesConfiguration[$packageKey]['suggestions'])) {
67 continue;
68 }
69 $dependencies[$packageKey] = [
70 'after' => []
71 ];
72 if (isset($packageStatesConfiguration[$packageKey]['dependencies'])) {
73 foreach ($packageStatesConfiguration[$packageKey]['dependencies'] as $dependentPackageKey) {
74 if (!in_array($dependentPackageKey, $packageKeys, true)) {
75 throw new \UnexpectedValueException(
76 'The package "' . $packageKey . '" depends on "'
77 . $dependentPackageKey . '" which is not present in the system.',
78 1382276561
79 );
80 }
81 $dependencies[$packageKey]['after'][] = $dependentPackageKey;
82 }
83 }
84 if (isset($packageStatesConfiguration[$packageKey]['suggestions'])) {
85 foreach ($packageStatesConfiguration[$packageKey]['suggestions'] as $suggestedPackageKey) {
86 // skip suggestions on not existing packages
87 if (in_array($suggestedPackageKey, $packageKeys, true)) {
88 // Suggestions actually have never been meant to influence loading order.
89 // We misuse this currently, as there is no other way to influence the loading order
90 // for not-required packages (soft-dependency).
91 // When considering suggestions for the loading order, we might create a cyclic dependency
92 // if the suggested package already has a real dependency on this package, so the suggestion
93 // has do be dropped in this case and must *not* be taken into account for loading order evaluation.
94 $dependencies[$packageKey]['after-resilient'][] = $suggestedPackageKey;
95 }
96 }
97 }
98 }
99 return $dependencies;
100 }
101
102 /**
103 * Adds all root packages of current dependency graph as dependency to all extensions
104 *
105 * This ensures that the framework extensions (aka sysext) are
106 * always loaded first, before any other external extension.
107 *
108 * @param array $packageStateConfiguration
109 * @param array $rootPackageKeys
110 * @return array
111 */
112 protected function addDependencyToFrameworkToAllExtensions(array $packageStateConfiguration, array $rootPackageKeys)
113 {
114 $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration);
115 $extensionPackageKeys = array_diff(array_keys($packageStateConfiguration), $frameworkPackageKeys);
116 foreach ($extensionPackageKeys as $packageKey) {
117 // Remove framework packages from list
118 $packageKeysWithoutFramework = array_diff(
119 $packageStateConfiguration[$packageKey]['dependencies'],
120 $frameworkPackageKeys
121 );
122 // The order of the array_merge is crucial here,
123 // we want the framework first
124 $packageStateConfiguration[$packageKey]['dependencies'] = array_merge(
125 $rootPackageKeys,
126 $packageKeysWithoutFramework
127 );
128 }
129 return $packageStateConfiguration;
130 }
131
132 /**
133 * Builds the dependency graph for all packages
134 *
135 * This method also introduces dependencies among the dependencies
136 * to ensure the loading order is exactly as specified in the list.
137 *
138 * @param array $packageStateConfiguration
139 * @return array
140 */
141 protected function buildDependencyGraph(array $packageStateConfiguration)
142 {
143 $frameworkPackageKeys = $this->findFrameworkPackages($packageStateConfiguration);
144 $frameworkPackagesDependencyGraph = $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $frameworkPackageKeys));
145 $packageStateConfiguration = $this->addDependencyToFrameworkToAllExtensions($packageStateConfiguration, $this->dependencyOrderingService->findRootIds($frameworkPackagesDependencyGraph));
146
147 $packageKeys = array_keys($packageStateConfiguration);
148 return $this->dependencyOrderingService->buildDependencyGraph($this->convertConfigurationForGraph($packageStateConfiguration, $packageKeys));
149 }
150
151 /**
152 * @param array $packageStateConfiguration
153 * @return array
154 */
155 protected function findFrameworkPackages(array $packageStateConfiguration)
156 {
157 $frameworkPackageKeys = [];
158 $packageManager = GeneralUtility::makeInstance(PackageManager::class);
159 foreach ($packageStateConfiguration as $packageKey => $packageConfiguration) {
160 /** @var Package $package */
161 $package = $packageManager->getPackage($packageKey);
162 if ($package->getValueFromComposerManifest('type') === 'typo3-cms-framework') {
163 $frameworkPackageKeys[] = $packageKey;
164 }
165 }
166
167 return $frameworkPackageKeys;
168 }
169 }