‪TYPO3CMS  ‪main
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 
96  public function buildDependencyGraph(array $dependencies, $beforeKey = 'before', $afterKey = 'after')
97  {
98  $dependencies = $this->‪prepareDependencies($dependencies, $beforeKey, $afterKey);
99 
100  ‪$identifiers = array_keys($dependencies);
101  sort(‪$identifiers);
102  // $dependencyGraph is the adjacency matrix as two-dimensional array initialized to FALSE (empty graph)
104  ‪$dependencyGraph = array_fill_keys(‪$identifiers, array_fill_keys(‪$identifiers, false));
105 
106  foreach (‪$identifiers as $id) {
107  foreach ($dependencies[$id][$beforeKey] as $beforeId) {
108  ‪$dependencyGraph[$beforeId][$id] = true;
109  }
110  foreach ($dependencies[$id][$afterKey] as $afterId) {
111  ‪$dependencyGraph[$id][$afterId] = true;
112  }
113  }
114 
115  // @internal PackageManager
116  // this is a dirty special case for suggestion handling of packages
117  // see \TYPO3\CMS\Core\Package\PackageManager::convertConfigurationForGraph for details
118  // DO NOT use this for any other case
119  foreach (‪$identifiers as $id) {
120  if (isset($dependencies[$id]['after-resilient'])) {
121  foreach ($dependencies[$id]['after-resilient'] as $afterId) {
122  $reverseDependencies = $this->‪findPathInGraph(‪$dependencyGraph, $afterId, $id);
123  if (empty($reverseDependencies)) {
124  ‪$dependencyGraph[$id][$afterId] = true;
125  }
126  }
127  }
128  }
129 
131  }
132 
140  public function ‪calculateOrder(array ‪$dependencyGraph)
141  {
142  $rootIds = array_flip($this->‪findRootIds($dependencyGraph));
143 
144  // Add number of dependencies for each root node
145  foreach ($rootIds as $id => &$dependencies) {
146  $dependencies = count(array_filter(‪$dependencyGraph[$id]));
147  }
148  unset($dependencies);
149 
150  // This will contain our final result in reverse order,
151  // meaning a result of [A, B, C] equals "A after B after C"
152  $sortedIds = [];
153 
154  // Walk through the graph, level by level
155  while (!empty($rootIds)) {
156  ksort($rootIds);
157  // We take those with fewer dependencies first, to have them at the end of the list in the final result.
158  $minimum = PHP_INT_MAX;
159  $currentId = 0;
160  foreach ($rootIds as $id => $count) {
161  if ($count <= $minimum) {
162  $minimum = $count;
163  $currentId = $id;
164  }
165  }
166  unset($rootIds[$currentId]);
167 
168  $sortedIds[] = $currentId;
169 
170  // Process the dependencies of the current node
171  foreach (array_filter(‪$dependencyGraph[$currentId]) as $dependingId => $_) {
172  // Remove the edge to this dependency
173  ‪$dependencyGraph[$currentId][$dependingId] = false;
174  if (!$this->‪getIncomingEdgeCount($dependencyGraph, $dependingId)) {
175  // We found a new root, lets add it to the list
176  $rootIds[$dependingId] = count(array_filter(‪$dependencyGraph[$dependingId]));
177  }
178  }
179  }
180 
181  // Check for remaining edges in the graph
182  $cycles = [];
183  array_walk(‪$dependencyGraph, static function ($dependencies, $fromId) use (&$cycles) {
184  array_walk($dependencies, static function ($dependency, $toId) use (&$cycles, $fromId) {
185  if ($dependency) {
186  $cycles[] = $fromId . '->' . $toId;
187  }
188  });
189  });
190  if (!empty($cycles)) {
191  throw new \UnexpectedValueException('Your dependencies have cycles. That will not work out. Cycles found: ' . implode(', ', $cycles), 1381960494);
192  }
193 
194  // We now built a list of dependencies
195  // Reverse the list to get the correct sorting order
196  return array_reverse($sortedIds);
197  }
198 
206  {
207  $incomingEdgeCount = 0;
208  foreach (‪$dependencyGraph as $dependencies) {
209  if ($dependencies[‪$identifier]) {
210  $incomingEdgeCount++;
211  }
212  }
213  return $incomingEdgeCount;
214  }
215 
225  public function ‪findRootIds(array ‪$dependencyGraph)
226  {
227  // Filter nodes with no incoming edge (aka root nodes)
228  $rootIds = [];
229  foreach (‪$dependencyGraph as $id => $_) {
230  if (!$this->‪getIncomingEdgeCount($dependencyGraph, $id)) {
231  $rootIds[] = $id;
232  }
233  }
234  return $rootIds;
235  }
236 
245  protected function ‪findPathInGraph(array $graph, $from, $to)
246  {
247  foreach (array_filter($graph[$from]) as $node => $_) {
248  if ($node === $to) {
249  return [$from, $to];
250  }
251  $subPath = $this->‪findPathInGraph($graph, $node, $to);
252  if (!empty($subPath)) {
253  array_unshift($subPath, $from);
254  return $subPath;
255  }
256  }
257  return [];
258  }
259 
272  protected function ‪prepareDependencies(array $dependencies, $beforeKey = 'before', $afterKey = 'after')
273  {
274  $preparedDependencies = [];
275  foreach ($dependencies as $id => $dependency) {
276  foreach ([$beforeKey, $afterKey] as $relation) {
277  if (!isset($dependency[$relation]) || !is_array($dependency[$relation])) {
278  $dependency[$relation] = [];
279  }
280  // add all missing, but referenced identifiers to the $dependency list
281  foreach ($dependency[$relation] as $dependingId) {
282  if (!isset($dependencies[$dependingId]) && !isset($preparedDependencies[$dependingId])) {
283  $preparedDependencies[$dependingId] = [
284  $beforeKey => [],
285  $afterKey => [],
286  ];
287  }
288  }
289  }
290  $preparedDependencies[$id] = $dependency;
291  }
292  return $preparedDependencies;
293  }
294 }
‪TYPO3\CMS\Core\Service\DependencyOrderingService\$dependencyGraph
‪foreach($identifiers as $id) foreach($identifiers as $id) return $dependencyGraph
Definition: DependencyOrderingService.php:119
‪TYPO3\CMS\Core\Service\DependencyOrderingService\findPathInGraph
‪array findPathInGraph(array $graph, $from, $to)
Definition: DependencyOrderingService.php:245
‪TYPO3\CMS\Core\Service\DependencyOrderingService\findRootIds
‪array findRootIds(array $dependencyGraph)
Definition: DependencyOrderingService.php:225
‪TYPO3\CMS\Core\Service\DependencyOrderingService\calculateOrder
‪mixed[] calculateOrder(array $dependencyGraph)
Definition: DependencyOrderingService.php:140
‪TYPO3\CMS\Core\Service\DependencyOrderingService\orderByDependencies
‪array orderByDependencies(array $items, $beforeKey='before', $afterKey='after')
Definition: DependencyOrderingService.php:51
‪TYPO3\CMS\Core\Service\DependencyOrderingService\$identifiers
‪$identifiers
Definition: DependencyOrderingService.php:100
‪TYPO3\CMS\Core\Service\DependencyOrderingService\$dependencyGraph
‪$dependencyGraph
Definition: DependencyOrderingService.php:104
‪TYPO3\CMS\Core\Service\DependencyOrderingService\prepareDependencies
‪array prepareDependencies(array $dependencies, $beforeKey='before', $afterKey='after')
Definition: DependencyOrderingService.php:272
‪TYPO3\CMS\Core\Service\DependencyOrderingService
Definition: DependencyOrderingService.php:32
‪TYPO3\CMS\Core\Service
‪TYPO3\CMS\Core\Service\DependencyOrderingService\prepareDependencies
‪array< array-key, buildDependencyGraph(array $dependencies, $beforeKey='before', $afterKey='after') { $dependencies=$this-> prepareDependencies($dependencies, $beforeKey, $afterKey)
‪TYPO3\CMS\Core\Service\DependencyOrderingService\getIncomingEdgeCount
‪int getIncomingEdgeCount(array $dependencyGraph, $identifier)
Definition: DependencyOrderingService.php:205
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37