arrangeMigrationsForDirection($direction, $this->getMigrations()); $availableMigrations = array_filter( $migrationsToCheck, // in_array third parameter is intentionally false to force object to string casting static fn (AvailableMigration $availableMigration): bool => in_array($availableMigration->getVersion(), $versions, false), ); $planItems = array_map(static fn (AvailableMigration $availableMigration): MigrationPlan => new MigrationPlan($availableMigration->getVersion(), $availableMigration->getMigration(), $direction), $availableMigrations); if (count($planItems) !== count($versions)) { $plannedVersions = array_map(static fn (MigrationPlan $migrationPlan): Version => $migrationPlan->getVersion(), $planItems); $diff = array_diff($versions, $plannedVersions); throw MigrationClassNotFound::new((string) reset($diff)); } return new MigrationPlanList($planItems, $direction); } public function getPlanUntilVersion(Version $to): MigrationPlanList { if ((string) $to !== '0' && ! $this->migrationRepository->hasMigration((string) $to)) { throw MigrationClassNotFound::new((string) $to); } $availableMigrations = $this->getMigrations(); // migrations are sorted at this point $executedMigrations = $this->metadataStorage->getExecutedMigrations(); $direction = $this->findDirection($to, $executedMigrations, $availableMigrations); $migrationsToCheck = $this->arrangeMigrationsForDirection($direction, $availableMigrations); $toExecute = $this->findMigrationsToExecute($to, $migrationsToCheck, $direction, $executedMigrations); return new MigrationPlanList(array_map(static fn (AvailableMigration $migration): MigrationPlan => new MigrationPlan($migration->getVersion(), $migration->getMigration(), $direction), $toExecute), $direction); } public function getMigrations(): AvailableMigrationsList { $availableMigrations = $this->migrationRepository->getMigrations()->getItems(); uasort($availableMigrations, fn (AvailableMigration $a, AvailableMigration $b): int => $this->sorter->compare($a->getVersion(), $b->getVersion())); return new AvailableMigrationsList($availableMigrations); } private function findDirection(Version $to, ExecutedMigrationsList $executedMigrations, AvailableMigrationsList $availableMigrations): string { if ((string) $to === '0') { return Direction::DOWN; } foreach ($availableMigrations->getItems() as $availableMigration) { if ($availableMigration->getVersion()->equals($to)) { break; } if (! $executedMigrations->hasMigration($availableMigration->getVersion())) { return Direction::UP; } } if ($executedMigrations->hasMigration($to) && ! $executedMigrations->getLast()->getVersion()->equals($to)) { return Direction::DOWN; } return Direction::UP; } /** @return AvailableMigration[] */ private function arrangeMigrationsForDirection(string $direction, Metadata\AvailableMigrationsList $availableMigrations): array { return $direction === Direction::UP ? $availableMigrations->getItems() : array_reverse($availableMigrations->getItems()); } /** * @param AvailableMigration[] $migrationsToCheck * * @return AvailableMigration[] */ private function findMigrationsToExecute(Version $to, array $migrationsToCheck, string $direction, ExecutedMigrationsList $executedMigrations): array { $toExecute = []; foreach ($migrationsToCheck as $availableMigration) { if ($direction === Direction::DOWN && $availableMigration->getVersion()->equals($to)) { break; } if ($direction === Direction::UP && ! $executedMigrations->hasMigration($availableMigration->getVersion())) { $toExecute[] = $availableMigration; } elseif ($direction === Direction::DOWN && $executedMigrations->hasMigration($availableMigration->getVersion())) { $toExecute[] = $availableMigration; } if ($direction === Direction::UP && $availableMigration->getVersion()->equals($to)) { break; } } return $toExecute; } }