‪TYPO3CMS  ‪main
ModuleRegistry.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
23 
27 final class ‪ModuleRegistry
28 {
32  private array ‪$modules = [];
33 
38  private array ‪$moduleAliases = [];
39 
43  public function ‪__construct(array ‪$modules)
44  {
45  array_walk(‪$modules, $this->‪addModule(...));
46  $this->modules = $this->‪applyHierarchy($this->modules);
47  $this->‪populateAliasMapping();
48  }
49 
50  private function ‪addModule(‪ModuleInterface $module): void
51  {
52  ‪$identifier = $module->‪getIdentifier();
53  if (isset($this->modules[‪$identifier])) {
54  throw new \LogicException(
55  'A module with the identifier ' . ‪$identifier . ' is already registered.',
56  1642174843
57  );
58  }
59  $this->modules[$module->‪getIdentifier()] = $module;
60  }
61 
62  public function ‪hasModule(string ‪$identifier): bool
63  {
64  return isset($this->modules[‪$identifier]) || isset($this->moduleAliases[‪$identifier]);
65  }
66 
68  {
69  if (!$this->‪hasModule($identifier)) {
70  throw new \InvalidArgumentException(
71  'Module with identifier ' . ‪$identifier . ' does not exist.',
72  1642375889
73  );
74  }
75 
76  // Resolve the alias to the real module
77  if (isset($this->moduleAliases[‪$identifier])) {
78  ‪$identifier = $this->moduleAliases[‪$identifier];
79  }
80  return $this->modules[‪$identifier];
81  }
82 
86  public function ‪getModules(): array
87  {
88  return ‪$this->modules;
89  }
90 
94  public function ‪registerRoutesForModules(‪Router $router): void
95  {
96  foreach ($this->modules as $module) {
97  if (!$module->hasParentModule() && !$module->isStandalone()) {
98  // Skip first level modules, which are not standalone
99  continue;
100  }
101  $routeCollection = new ‪RouteCollection();
102  foreach ($module->getDefaultRouteOptions() as $routeIdentifier => $routeOptions) {
103  $path = (string)(($routeOptions['path'] ?? false) ?: ('/' . $routeIdentifier));
104  $methods = (array)($routeOptions['methods'] ?? []);
105  unset($routeOptions['path'], $routeOptions['methods']);
106  if ($routeIdentifier === '_default') {
107  // Add the first
108  $route = new ‪Route($module->getPath(), $routeOptions);
109  if ($methods !== []) {
110  $route->setMethods($methods);
111  }
112  $router->‪addRoute($module->getIdentifier(), $route, $module->getAliases());
113  } else {
114  $route = new ‪Route($path, $routeOptions);
115  if ($methods !== []) {
116  $route->setMethods($methods);
117  }
118  $routeCollection->add($routeIdentifier, $route);
119  }
120  }
121  $routeCollection->addNamePrefix($module->getIdentifier() . '.');
122  $routeCollection->addPrefix($module->getPath());
123  $router->‪addRouteCollection($routeCollection);
124  }
125  }
126 
135  protected function ‪applyHierarchy(array ‪$modules): array
136  {
137  // Fetch top-level (parent) modules and fill them with sorted sub modules
138  $topLevelModules = [];
139  foreach (‪$modules as ‪$identifier => $module) {
140  if ($module->getParentIdentifier() === '') {
141  $topLevelModules[‪$identifier] = $module;
142  }
143  $subModules = array_filter(
144  ‪$modules,
145  static fn(‪ModuleInterface $mod): bool => $mod->‪getParentIdentifier() === ‪$identifier
146  );
147  if ($subModules === []) {
148  continue;
149  }
150  // Sort sub modules and connect them with their parent module
151  $subModules = $this->‪applySorting($subModules);
152  foreach ($subModules as $subModule) {
153  $module->addSubModule($subModule);
154  $subModule->setParentModule($module);
155  }
156  }
157  // Sort top level modules and return all modules (flat) with the correct sorting
158  return $this->‪flattenModules($this->‪applySorting($topLevelModules));
159  }
160 
168  protected function ‪applySorting(array ‪$modules): array
169  {
170  $modulePositionInformation = [];
171  // First create a list of all needed data, that is the identifier, and its position
172  foreach (‪$modules as ‪$identifier => $module) {
173  // @todo Should we enforce ['after' => '*'] in case ->getPosition() is empty?
174  $modulePositionInformation[‪$identifier] = $module->getPosition();
175  $modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyBefore'] = [];
176  $modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyAfter'] = [];
177  }
178 
179  // Identifiers of modules to be added at the top
180  $highPriorityModules = [];
181  // Identifiers of modules to be added at the bottom
182  $lowPriorityModules = [];
183 
184  // Sort out the "top" and "bottom", and also build a graph of directly dependant (before/after) modules
185  foreach ($modulePositionInformation as ‪$identifier => $positionInformation) {
186  if ($positionInformation['before'] ?? false) {
187  if ($positionInformation['before'] === '*') {
188  // Module should be added on top
189  $highPriorityModules[] = ‪$identifier;
190  } elseif (isset(‪$modules[$positionInformation['before']])) {
191  // Build the dependencies in case a valid module identifier is configured
192  $modulePositionInformation[$positionInformation['before']]['modulesToBeAddedDirectlyBefore'][] = ‪$identifier;
193  $modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyAfter'][] = $positionInformation['before'];
194  }
195  } elseif ($positionInformation['after'] ?? false) {
196  if ($positionInformation['after'] === '*') {
197  // Module should be added at the bottom
198  $lowPriorityModules[] = ‪$identifier;
199  } elseif (isset(‪$modules[$positionInformation['after']])) {
200  // Build the dependencies in case a valid module identifier is configured
201  $modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyBefore'][] = $positionInformation['after'];
202  $modulePositionInformation[$positionInformation['after']]['modulesToBeAddedDirectlyAfter'][] = ‪$identifier;
203  }
204  }
205  }
206 
207  // First add the top items and their dependant modules
208  $orderedModuleIdentifiers = $this->‪populateOrderingsForDependencies($highPriorityModules, $modulePositionInformation);
209  // Now add the bottom items and their dependant modules
210  // They will be cut out later, however, this is done now to also add their dependencies NOW (and not when looping over all items again)
211  $orderedModuleIdentifiers = $this->‪populateOrderingsForDependencies($lowPriorityModules, $modulePositionInformation, $orderedModuleIdentifiers);
212  $lastLowPriorityModule = end($orderedModuleIdentifiers);
213  // Loop through all items and see which have not been added yet. Keep the original sorting.
214  $orderedModuleIdentifiers = $this->‪populateOrderingsForDependencies(array_keys($modulePositionInformation), $modulePositionInformation, $orderedModuleIdentifiers);
215  // Find the lowest priority module and move everything after that module to the very end
216  if ($lowPriorityModules !== [] && $lastLowPriorityModule) {
217  $firstLowPriorityModule = reset($lowPriorityModules);
218  if ($firstLowPriorityModule !== $lastLowPriorityModule) {
219  $firstPosition = array_search($firstLowPriorityModule, $orderedModuleIdentifiers, true);
220  $lastPosition = array_search($lastLowPriorityModule, $orderedModuleIdentifiers, true);
221  if ($firstPosition !== false && $lastPosition !== false) {
222  $extractedItems = array_slice($orderedModuleIdentifiers, $firstPosition, $lastPosition);
223  $orderedModuleIdentifiers = array_merge($orderedModuleIdentifiers, $extractedItems);
224  }
225  }
226  }
227  // Use the ordered list and replace all valid identifiers with the corresponding
228  return array_replace(array_intersect_key(array_flip($orderedModuleIdentifiers), ‪$modules), ‪$modules);
229  }
230 
238  array $moduleIdentifiersToBeAdded,
239  array $modulePositionInformation,
240  array $alreadyOrderedModuleIdentifiers = []
241  ): array {
242  foreach ($moduleIdentifiersToBeAdded as ‪$identifier) {
243  // already placed somewhere
244  if (in_array(‪$identifier, $alreadyOrderedModuleIdentifiers, true)) {
245  continue;
246  }
247  // Check if the current module has dependencies, which should be added BEFORE
248  foreach ($modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyBefore'] ?? [] as $dependantIdentifier) {
249  // already placed somewhere
250  if (in_array($dependantIdentifier, $alreadyOrderedModuleIdentifiers, true)) {
251  continue;
252  }
253  // Check if the dependent module has dependencies, which should be added BEFORE
254  foreach ($modulePositionInformation[$dependantIdentifier]['modulesToBeAddedDirectlyBefore'] ?? [] as $dependantDependantIdentifier) {
255  // already placed somewhere
256  if (in_array($dependantDependantIdentifier, $alreadyOrderedModuleIdentifiers, true)) {
257  continue;
258  }
259  // Add the sub dependency right away
260  $alreadyOrderedModuleIdentifiers[] = $dependantDependantIdentifier;
261  }
262  // Add the dependant module now
263  $alreadyOrderedModuleIdentifiers[] = $dependantIdentifier;
264  }
265  // Add the actual module now
266  $alreadyOrderedModuleIdentifiers[] = ‪$identifier;
267  // Check if the current module has dependencies, which should be added AFTER
268  foreach ($modulePositionInformation[‪$identifier]['modulesToBeAddedDirectlyAfter'] ?? [] as $dependantIdentifier) {
269  // already placed somewhere
270  if (in_array($dependantIdentifier, $alreadyOrderedModuleIdentifiers, true)) {
271  continue;
272  }
273  // Add the dependant module right away
274  $alreadyOrderedModuleIdentifiers[] = $dependantIdentifier;
275  // Check if the dependent module has dependencies, which should be added AFTER
276  foreach ($modulePositionInformation[$dependantIdentifier]['modulesToBeAddedDirectlyAfter'] ?? [] as $dependantDependantIdentifier) {
277  // already placed somewhere
278  if (in_array($dependantDependantIdentifier, $alreadyOrderedModuleIdentifiers, true)) {
279  continue;
280  }
281  // Add the sub dependency right away
282  $alreadyOrderedModuleIdentifiers[] = $dependantDependantIdentifier;
283  }
284  }
285  }
286  return $alreadyOrderedModuleIdentifiers;
287  }
288 
292  protected function ‪flattenModules(array ‪$modules, $flatModules = []): array
293  {
294  foreach (‪$modules as $module) {
295  $flatModules[$module->getIdentifier()] = $module;
296  if ($module->hasSubmodules()) {
297  $flatModules = $this->‪flattenModules($module->getSubmodules(), $flatModules);
298  }
299  }
300  return $flatModules;
301  }
302 
303  protected function ‪populateAliasMapping(): void
304  {
305  foreach ($this->modules as $moduleIdentifier => $module) {
306  foreach ($module->getAliases() as $aliasIdentifier) {
307  // Note: The last module defining the same alias wins in general
308  $this->moduleAliases[$aliasIdentifier] = $moduleIdentifier;
309  }
310  }
311  }
312 
313  public function ‪getModuleAliases(): array
314  {
316  }
317 }
‪TYPO3\CMS\Backend\Module\ModuleRegistry\applyHierarchy
‪ModuleInterface[] applyHierarchy(array $modules)
Definition: ModuleRegistry.php:135
‪TYPO3\CMS\Backend\Module\ModuleRegistry\getModules
‪ModuleInterface[] getModules()
Definition: ModuleRegistry.php:86
‪TYPO3\CMS\Backend\Module\ModuleRegistry\getModuleAliases
‪getModuleAliases()
Definition: ModuleRegistry.php:313
‪TYPO3\CMS\Backend\Module\ModuleRegistry\populateOrderingsForDependencies
‪populateOrderingsForDependencies(array $moduleIdentifiersToBeAdded, array $modulePositionInformation, array $alreadyOrderedModuleIdentifiers=[])
Definition: ModuleRegistry.php:237
‪TYPO3\CMS\Backend\Routing\Router\addRouteCollection
‪addRouteCollection(RouteCollection $routeCollection)
Definition: Router.php:66
‪TYPO3\CMS\Backend\Module\ModuleRegistry
Definition: ModuleRegistry.php:28
‪TYPO3\CMS\Backend\Module\ModuleRegistry\$modules
‪array $modules
Definition: ModuleRegistry.php:32
‪TYPO3\CMS\Backend\Module\ModuleRegistry\flattenModules
‪flattenModules(array $modules, $flatModules=[])
Definition: ModuleRegistry.php:292
‪TYPO3\CMS\Backend\Routing\Router\addRoute
‪addRoute(string $routeIdentifier, Route $route, array $aliases=[])
Definition: Router.php:56
‪TYPO3\CMS\Core\Routing\RouteCollection
Definition: RouteCollection.php:30
‪TYPO3\CMS\Backend\Routing\Route
Definition: Route.php:24
‪TYPO3\CMS\Backend\Module\ModuleRegistry\addModule
‪addModule(ModuleInterface $module)
Definition: ModuleRegistry.php:50
‪TYPO3\CMS\Backend\Module\ModuleInterface\getIdentifier
‪getIdentifier()
‪TYPO3\CMS\Backend\Module\ModuleInterface
Definition: ModuleInterface.php:24
‪TYPO3\CMS\Backend\Module\ModuleRegistry\applySorting
‪ModuleInterface[] applySorting(array $modules)
Definition: ModuleRegistry.php:168
‪TYPO3\CMS\Backend\Module\ModuleRegistry\getModule
‪getModule(string $identifier)
Definition: ModuleRegistry.php:67
‪TYPO3\CMS\Backend\Module\ModuleRegistry\hasModule
‪hasModule(string $identifier)
Definition: ModuleRegistry.php:62
‪TYPO3\CMS\Backend\Module\ModuleRegistry\populateAliasMapping
‪populateAliasMapping()
Definition: ModuleRegistry.php:303
‪TYPO3\CMS\Backend\Module\ModuleRegistry\__construct
‪__construct(array $modules)
Definition: ModuleRegistry.php:43
‪TYPO3\CMS\Backend\Module\ModuleRegistry\registerRoutesForModules
‪registerRoutesForModules(Router $router)
Definition: ModuleRegistry.php:94
‪TYPO3\CMS\Backend\Module\ModuleInterface\getParentIdentifier
‪getParentIdentifier()
‪TYPO3\CMS\Backend\Routing\Router
Definition: Router.php:40
‪TYPO3\CMS\Backend\Module
Definition: BaseModule.php:18
‪TYPO3\CMS\Backend\Module\ModuleRegistry\$moduleAliases
‪array $moduleAliases
Definition: ModuleRegistry.php:38
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37