AppScriptSort.php 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OC;
  7. use Psr\Log\LoggerInterface;
  8. /**
  9. * Sort scripts topologically by their dependencies
  10. * Implementation based on https://github.com/marcj/topsort.php
  11. */
  12. class AppScriptSort {
  13. /** @var LoggerInterface */
  14. private $logger;
  15. public function __construct(LoggerInterface $logger) {
  16. $this->logger = $logger;
  17. }
  18. /**
  19. * Recursive topological sorting
  20. *
  21. * @param AppScriptDependency $app
  22. * @param array $parents
  23. * @param array $scriptDeps
  24. * @param array $sortedScriptDeps
  25. */
  26. private function topSortVisit(
  27. AppScriptDependency $app,
  28. array &$parents,
  29. array &$scriptDeps,
  30. array &$sortedScriptDeps): void {
  31. // Detect and log circular dependencies
  32. if (isset($parents[$app->getId()])) {
  33. $this->logger->error('Circular dependency in app scripts at app ' . $app->getId());
  34. }
  35. // If app has not been visited
  36. if (!$app->isVisited()) {
  37. $parents[$app->getId()] = true;
  38. $app->setVisited(true);
  39. foreach ($app->getDeps() as $dep) {
  40. if ($app->getId() === $dep) {
  41. // Ignore dependency on itself
  42. continue;
  43. }
  44. if (isset($scriptDeps[$dep])) {
  45. $newParents = $parents;
  46. $this->topSortVisit($scriptDeps[$dep], $newParents, $scriptDeps, $sortedScriptDeps);
  47. }
  48. }
  49. $sortedScriptDeps[] = $app->getId();
  50. }
  51. }
  52. /**
  53. * @return array scripts sorted by dependencies
  54. */
  55. public function sort(array $scripts, array $scriptDeps): array {
  56. // Sort scriptDeps into sortedScriptDeps
  57. $sortedScriptDeps = [];
  58. foreach ($scriptDeps as $app) {
  59. $parents = [];
  60. $this->topSortVisit($app, $parents, $scriptDeps, $sortedScriptDeps);
  61. }
  62. // Sort scripts into sortedScripts based on sortedScriptDeps order
  63. $sortedScripts = [];
  64. foreach ($sortedScriptDeps as $app) {
  65. $sortedScripts[$app] = $scripts[$app] ?? [];
  66. }
  67. // Add remaining scripts
  68. foreach (array_keys($scripts) as $app) {
  69. if (!isset($sortedScripts[$app])) {
  70. $sortedScripts[$app] = $scripts[$app];
  71. }
  72. }
  73. return $sortedScripts;
  74. }
  75. }