‪TYPO3CMS  10.4
DependencyOrderingService.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
32 {
51  public function ‪orderByDependencies(array $items, $beforeKey = 'before', $afterKey = 'after')
52  {
53  $graph = $this->‪buildDependencyGraph($items, $beforeKey, $afterKey);
54  $sortedItems = [];
55  foreach ($this->‪calculateOrder($graph) as $id) {
56  if (isset($items[$id])) {
57  $sortedItems[$id] = $items[$id];
58  }
59  }
60  return $sortedItems;
61  }
62 
89  public function ‪buildDependencyGraph(array $dependencies, $beforeKey = 'before', $afterKey = 'after')
90  {
91  $dependencies = $this->‪prepareDependencies($dependencies, $beforeKey, $afterKey);
92 
93  $identifiers = array_keys($dependencies);
94  sort($identifiers);
95  // $dependencyGraph is the adjacency matrix as two-dimensional array initialized to FALSE (empty graph)
97  $dependencyGraph = array_fill_keys($identifiers, array_fill_keys($identifiers, false));
98 
99  foreach ($identifiers as $id) {
100  foreach ($dependencies[$id][$beforeKey] as $beforeId) {
101  $dependencyGraph[$beforeId][$id] = true;
102  }
103  foreach ($dependencies[$id][$afterKey] as $afterId) {
104  $dependencyGraph[$id][$afterId] = true;
105  }
106  }
107 
108  // @internal PackageManager
109  // this is a dirty special case for suggestion handling of packages
110  // see \TYPO3\CMS\Core\Package\PackageManager::convertConfigurationForGraph for details
111  // DO NOT use this for any other case
112  foreach ($identifiers as $id) {
113  if (isset($dependencies[$id]['after-resilient'])) {
114  foreach ($dependencies[$id]['after-resilient'] as $afterId) {
115  $reverseDependencies = $this->‪findPathInGraph($dependencyGraph, $afterId, $id);
116  if (empty($reverseDependencies)) {
117  $dependencyGraph[$id][$afterId] = true;
118  }
119  }
120  }
121  }
122 
123  return $dependencyGraph;
124  }
125 
133  public function ‪calculateOrder(array $dependencyGraph)
134  {
135  $rootIds = array_flip($this->‪findRootIds($dependencyGraph));
136 
137  // Add number of dependencies for each root node
138  foreach ($rootIds as $id => &$dependencies) {
139  $dependencies = count(array_filter($dependencyGraph[$id]));
140  }
141  unset($dependencies);
142 
143  // This will contain our final result in reverse order,
144  // meaning a result of [A, B, C] equals "A after B after C"
145  $sortedIds = [];
146 
147  // Walk through the graph, level by level
148  while (!empty($rootIds)) {
149  ksort($rootIds);
150  // We take those with fewer dependencies first, to have them at the end of the list in the final result.
151  $minimum = PHP_INT_MAX;
152  $currentId = 0;
153  foreach ($rootIds as $id => $count) {
154  if ($count <= $minimum) {
155  $minimum = $count;
156  $currentId = $id;
157  }
158  }
159  unset($rootIds[$currentId]);
160 
161  $sortedIds[] = $currentId;
162 
163  // Process the dependencies of the current node
164  foreach (array_filter($dependencyGraph[$currentId]) as $dependingId => $_) {
165  // Remove the edge to this dependency
166  $dependencyGraph[$currentId][$dependingId] = false;
167  if (!$this->‪getIncomingEdgeCount($dependencyGraph, $dependingId)) {
168  // We found a new root, lets add it to the list
169  $rootIds[$dependingId] = count(array_filter($dependencyGraph[$dependingId]));
170  }
171  }
172  }
173 
174  // Check for remaining edges in the graph
175  $cycles = [];
176  array_walk($dependencyGraph, function ($dependencies, $fromId) use (&$cycles) {
177  array_walk($dependencies, function ($dependency, $toId) use (&$cycles, $fromId) {
178  if ($dependency) {
179  $cycles[] = $fromId . '->' . $toId;
180  }
181  });
182  });
183  if (!empty($cycles)) {
184  throw new \UnexpectedValueException('Your dependencies have cycles. That will not work out. Cycles found: ' . implode(', ', $cycles), 1381960494);
185  }
186 
187  // We now built a list of dependencies
188  // Reverse the list to get the correct sorting order
189  return array_reverse($sortedIds);
190  }
191 
199  protected function ‪getIncomingEdgeCount(array $dependencyGraph, $identifier)
200  {
201  $incomingEdgeCount = 0;
202  foreach ($dependencyGraph as $dependencies) {
203  if ($dependencies[$identifier]) {
204  $incomingEdgeCount++;
205  }
206  }
207  return $incomingEdgeCount;
208  }
209 
219  public function ‪findRootIds(array $dependencyGraph)
220  {
221  // Filter nodes with no incoming edge (aka root nodes)
222  $rootIds = [];
223  foreach ($dependencyGraph as $id => $_) {
224  if (!$this->‪getIncomingEdgeCount($dependencyGraph, $id)) {
225  $rootIds[] = $id;
226  }
227  }
228  return $rootIds;
229  }
230 
239  protected function ‪findPathInGraph(array $graph, $from, $to)
240  {
241  foreach (array_filter($graph[$from]) as $node => $_) {
242  if ($node === $to) {
243  return [$from, $to];
244  }
245  $subPath = $this->‪findPathInGraph($graph, $node, $to);
246  if (!empty($subPath)) {
247  array_unshift($subPath, $from);
248  return $subPath;
249  }
250  }
251  return [];
252  }
253 
266  protected function ‪prepareDependencies(array $dependencies, $beforeKey = 'before', $afterKey = 'after')
267  {
268  $preparedDependencies = [];
269  foreach ($dependencies as $id => $dependency) {
270  foreach ([$beforeKey, $afterKey] as $relation) {
271  if (!isset($dependency[$relation]) || !is_array($dependency[$relation])) {
272  $dependency[$relation] = [];
273  }
274  // add all missing, but referenced identifiers to the $dependency list
275  foreach ($dependency[$relation] as $dependingId) {
276  if (!isset($dependencies[$dependingId]) && !isset($preparedDependencies[$dependingId])) {
277  $preparedDependencies[$dependingId] = [
278  $beforeKey => [],
279  $afterKey => []
280  ];
281  }
282  }
283  }
284  $preparedDependencies[$id] = $dependency;
285  }
286  return $preparedDependencies;
287  }
288 }
‪TYPO3\CMS\Core\Service\DependencyOrderingService\findPathInGraph
‪array findPathInGraph(array $graph, $from, $to)
Definition: DependencyOrderingService.php:239
‪TYPO3\CMS\Core\Service\DependencyOrderingService\findRootIds
‪array findRootIds(array $dependencyGraph)
Definition: DependencyOrderingService.php:219
‪TYPO3\CMS\Core\Service\DependencyOrderingService\calculateOrder
‪mixed[] calculateOrder(array $dependencyGraph)
Definition: DependencyOrderingService.php:133
‪TYPO3\CMS\Core\Service\DependencyOrderingService\orderByDependencies
‪array orderByDependencies(array $items, $beforeKey='before', $afterKey='after')
Definition: DependencyOrderingService.php:51
‪TYPO3\CMS\Core\Service\DependencyOrderingService\prepareDependencies
‪array prepareDependencies(array $dependencies, $beforeKey='before', $afterKey='after')
Definition: DependencyOrderingService.php:266
‪TYPO3\CMS\Core\Service\DependencyOrderingService
Definition: DependencyOrderingService.php:32
‪TYPO3\CMS\Core\Service
Definition: AbstractService.php:16
‪TYPO3\CMS\Core\Service\DependencyOrderingService\getIncomingEdgeCount
‪int getIncomingEdgeCount(array $dependencyGraph, $identifier)
Definition: DependencyOrderingService.php:199
‪TYPO3\CMS\Core\Service\DependencyOrderingService\buildDependencyGraph
‪bool[][] buildDependencyGraph(array $dependencies, $beforeKey='before', $afterKey='after')
Definition: DependencyOrderingService.php:89