* @since 30.0.0 */ public function extractMigrationAttributes(string $appId): array { $ms = new MigrationService($appId, $this->connection); $metadata = []; foreach ($ms->getAvailableVersions() as $version) { $metadata[$version] = []; $class = new ReflectionClass($ms->createInstance($version)); $attributes = $class->getAttributes(); foreach ($attributes as $attribute) { $item = $attribute->newInstance(); if ($item instanceof MigrationAttribute) { $metadata[$version][] = $item; } } } return $metadata; } /** * convert direct data from release metadata into a list of Migrations' Attribute * * @param array> $metadata * @param bool $filterKnownMigrations ignore metadata already done in local instance * * @return array{apps: array>, core: array} * @since 30.0.0 */ public function getMigrationsAttributesFromReleaseMetadata( array $metadata, bool $filterKnownMigrations = false, ): array { $appsAttributes = []; foreach (array_keys($metadata['apps']) as $appId) { if ($filterKnownMigrations && !$this->appManager->isInstalled($appId)) { continue; // if not interested and app is not installed } $done = ($filterKnownMigrations) ? $this->getKnownMigrations($appId) : []; $appsAttributes[$appId] = $this->parseMigrations($metadata['apps'][$appId] ?? [], $done); } $done = ($filterKnownMigrations) ? $this->getKnownMigrations('core') : []; return [ 'core' => $this->parseMigrations($metadata['core'] ?? [], $done), 'apps' => $appsAttributes ]; } /** * returns list of installed apps that does not support migrations metadata (yet) * * @param array> $metadata * * @return string[] * @since 30.0.0 */ public function getUnsupportedApps(array $metadata): array { return array_values(array_diff($this->appManager->getInstalledApps(), array_keys($metadata['apps']))); } /** * convert raw data to a list of MigrationAttribute * * @param array $migrations * @param array $ignoreMigrations * * @return array */ private function parseMigrations(array $migrations, array $ignoreMigrations = []): array { $parsed = []; foreach (array_keys($migrations) as $entry) { if (in_array($entry, $ignoreMigrations)) { continue; } $parsed[$entry] = []; foreach ($migrations[$entry] as $item) { try { $parsed[$entry][] = $this->createAttribute($item); } catch (AttributeException $e) { $this->logger->warning('exception while trying to create attribute', ['exception' => $e, 'item' => json_encode($item)]); $parsed[$entry][] = new GenericMigrationAttribute($item); } } } return $parsed; } /** * returns migrations already done * * @param string $appId * * @return array * @throws \Exception */ private function getKnownMigrations(string $appId): array { $ms = new MigrationService($appId, $this->connection); return $ms->getMigratedVersions(); } /** * generate (deserialize) a MigrationAttribute from a serialized version * * @param array $item * * @return MigrationAttribute * @throws AttributeException */ private function createAttribute(array $item): MigrationAttribute { $class = $item['class'] ?? ''; $namespace = 'OCP\Migration\Attributes\\'; if (!str_starts_with($class, $namespace) || !ctype_alpha(substr($class, strlen($namespace)))) { throw new AttributeException('class name does not looks valid'); } try { $attribute = new $class($item['table'] ?? ''); return $attribute->import($item); } catch (\Error) { throw new AttributeException('cannot import Attribute'); } } }