MetadataManager.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Migration;
  8. use OC\DB\Connection;
  9. use OC\DB\MigrationService;
  10. use OC\Migration\Exceptions\AttributeException;
  11. use OCP\App\IAppManager;
  12. use OCP\Migration\Attributes\GenericMigrationAttribute;
  13. use OCP\Migration\Attributes\MigrationAttribute;
  14. use Psr\Log\LoggerInterface;
  15. /**
  16. * Helps managing DB Migrations' Metadata
  17. *
  18. * @since 30.0.0
  19. */
  20. class MetadataManager {
  21. public function __construct(
  22. private IAppManager $appManager,
  23. private Connection $connection,
  24. private LoggerInterface $logger,
  25. ) {
  26. }
  27. /**
  28. * convert direct data from release metadata into a list of Migrations' Attribute
  29. *
  30. * @param array<array-key, array<array-key, array>> $metadata
  31. * @param bool $filterKnownMigrations ignore metadata already done in local instance
  32. *
  33. * @return array{apps: array<array-key, array<string, MigrationAttribute[]>>, core: array<string, MigrationAttribute[]>}
  34. * @since 30.0.0
  35. */
  36. public function getMigrationsAttributesFromReleaseMetadata(
  37. array $metadata,
  38. bool $filterKnownMigrations = false
  39. ): array {
  40. $appsAttributes = [];
  41. foreach (array_keys($metadata['apps']) as $appId) {
  42. if ($filterKnownMigrations && !$this->appManager->isInstalled($appId)) {
  43. continue; // if not interested and app is not installed
  44. }
  45. $done = ($filterKnownMigrations) ? $this->getKnownMigrations($appId) : [];
  46. $appsAttributes[$appId] = $this->parseMigrations($metadata['apps'][$appId] ?? [], $done);
  47. }
  48. $done = ($filterKnownMigrations) ? $this->getKnownMigrations('core') : [];
  49. return [
  50. 'core' => $this->parseMigrations($metadata['core'] ?? [], $done),
  51. 'apps' => $appsAttributes
  52. ];
  53. }
  54. /**
  55. * returns list of installed apps that does not support migrations metadata (yet)
  56. *
  57. * @param array<array-key, array<array-key, array>> $metadata
  58. *
  59. * @return string[]
  60. * @since 30.0.0
  61. */
  62. public function getUnsupportedApps(array $metadata): array {
  63. return array_values(array_diff($this->appManager->getInstalledApps(), array_keys($metadata['apps'])));
  64. }
  65. /**
  66. * convert raw data to a list of MigrationAttribute
  67. *
  68. * @param array $migrations
  69. * @param array $ignoreMigrations
  70. *
  71. * @return array<string, MigrationAttribute[]>
  72. */
  73. private function parseMigrations(array $migrations, array $ignoreMigrations = []): array {
  74. $parsed = [];
  75. foreach (array_keys($migrations) as $entry) {
  76. if (in_array($entry, $ignoreMigrations)) {
  77. continue;
  78. }
  79. $parsed[$entry] = [];
  80. foreach ($migrations[$entry] as $item) {
  81. try {
  82. $parsed[$entry][] = $this->createAttribute($item);
  83. } catch (AttributeException $e) {
  84. $this->logger->warning('exception while trying to create attribute', ['exception' => $e, 'item' => json_encode($item)]);
  85. $parsed[$entry][] = new GenericMigrationAttribute($item);
  86. }
  87. }
  88. }
  89. return $parsed;
  90. }
  91. /**
  92. * returns migrations already done
  93. *
  94. * @param string $appId
  95. *
  96. * @return array
  97. * @throws \Exception
  98. */
  99. private function getKnownMigrations(string $appId): array {
  100. $ms = new MigrationService($appId, $this->connection);
  101. return $ms->getMigratedVersions();
  102. }
  103. /**
  104. * generate (deserialize) a MigrationAttribute from a serialized version
  105. *
  106. * @param array $item
  107. *
  108. * @return MigrationAttribute
  109. * @throws AttributeException
  110. */
  111. private function createAttribute(array $item): MigrationAttribute {
  112. $class = $item['class'] ?? '';
  113. $namespace = 'OCP\Migration\Attributes\\';
  114. if (!str_starts_with($class, $namespace)
  115. || !ctype_alpha(substr($class, strlen($namespace)))) {
  116. throw new AttributeException('class name does not looks valid');
  117. }
  118. try {
  119. $attribute = new $class($item['table'] ?? '');
  120. return $attribute->import($item);
  121. } catch (\Error) {
  122. throw new AttributeException('cannot import Attribute');
  123. }
  124. }
  125. }