The start of something beautiful

This commit is contained in:
2024-09-11 22:48:07 -06:00
parent 45acea47f3
commit f5997ee5ec
5614 changed files with 630696 additions and 0 deletions
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
/**
* The DefaultAliasResolver class is responsible for resolving aliases like first, current, etc. to the actual version number.
*
* @internal
*/
interface AliasResolver
{
/**
* Returns the version number from an alias.
*
* Supported aliases are:
*
* - first: The very first version before any migrations have been run.
* - current: The current version.
* - prev: The version prior to the current version.
* - next: The version following the current version.
* - latest: The latest available version.
*
* If an existing version number is specified, it is returned verbatim.
*/
public function resolveVersionAlias(string $alias): Version;
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use function strcmp;
final class AlphabeticalComparator implements Comparator
{
public function compare(Version $a, Version $b): int
{
return strcmp((string) $a, (string) $b);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
interface Comparator
{
public function compare(Version $a, Version $b): int;
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
/**
* The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current
* version to another version.
*/
final class CurrentMigrationStatusCalculator implements MigrationStatusCalculator
{
public function __construct(
private readonly MigrationPlanCalculator $migrationPlanCalculator,
private readonly MetadataStorage $metadataStorage,
) {
}
public function getExecutedUnavailableMigrations(): ExecutedMigrationsList
{
$executedMigrations = $this->metadataStorage->getExecutedMigrations();
$availableMigration = $this->migrationPlanCalculator->getMigrations();
return $executedMigrations->unavailableSubset($availableMigration);
}
public function getNewMigrations(): AvailableMigrationsList
{
$executedMigrations = $this->metadataStorage->getExecutedMigrations();
$availableMigration = $this->migrationPlanCalculator->getMigrations();
return $availableMigration->newSubset($executedMigrations);
}
}
+324
View File
@@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\EventDispatcher;
use Doctrine\Migrations\Events;
use Doctrine\Migrations\Exception\SkipMigration;
use Doctrine\Migrations\Metadata\MigrationPlan;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\MigratorConfiguration;
use Doctrine\Migrations\ParameterFormatter;
use Doctrine\Migrations\Provider\SchemaDiffProvider;
use Doctrine\Migrations\Query\Query;
use Doctrine\Migrations\Tools\BytesFormatter;
use Doctrine\Migrations\Tools\TransactionHelper;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Stopwatch\Stopwatch;
use Throwable;
use function count;
use function method_exists;
use function ucfirst;
/**
* The DbalExecutor class is responsible for executing a single migration version.
*
* @internal
*/
final class DbalExecutor implements Executor
{
/** @var Query[] */
private array $sql = [];
public function __construct(
private readonly MetadataStorage $metadataStorage,
private readonly EventDispatcher $dispatcher,
private readonly Connection $connection,
private readonly SchemaDiffProvider $schemaProvider,
private readonly LoggerInterface $logger,
private readonly ParameterFormatter $parameterFormatter,
private readonly Stopwatch $stopwatch,
) {
}
/** @return Query[] */
public function getSql(): array
{
return $this->sql;
}
public function addSql(Query $sqlQuery): void
{
$this->sql[] = $sqlQuery;
}
public function execute(
MigrationPlan $plan,
MigratorConfiguration $configuration,
): ExecutionResult {
$result = new ExecutionResult($plan->getVersion(), $plan->getDirection(), new DateTimeImmutable());
$this->startMigration($plan, $configuration);
try {
$this->executeMigration(
$plan,
$result,
$configuration,
);
$result->setSql($this->sql);
} catch (SkipMigration $e) {
$result->setSkipped(true);
$this->migrationEnd($e, $plan, $result, $configuration);
} catch (Throwable $e) {
$result->setError(true, $e);
$this->migrationEnd($e, $plan, $result, $configuration);
throw $e;
}
return $result;
}
private function startMigration(
MigrationPlan $plan,
MigratorConfiguration $configuration,
): void {
$this->sql = [];
$this->dispatcher->dispatchVersionEvent(
Events::onMigrationsVersionExecuting,
$plan,
$configuration,
);
if (! $plan->getMigration()->isTransactional()) {
return;
}
// only start transaction if in transactional mode
$this->connection->beginTransaction();
}
private function executeMigration(
MigrationPlan $plan,
ExecutionResult $result,
MigratorConfiguration $configuration,
): ExecutionResult {
$stopwatchEvent = $this->stopwatch->start('execute');
$migration = $plan->getMigration();
$direction = $plan->getDirection();
$result->setState(State::PRE);
$fromSchema = $this->getFromSchema($configuration);
$migration->{'pre' . ucfirst($direction)}($fromSchema);
$this->logger->info(...$this->getMigrationHeader($plan, $migration, $direction));
$result->setState(State::EXEC);
$toSchema = $this->schemaProvider->createToSchema($fromSchema);
$result->setToSchema($toSchema);
$migration->$direction($toSchema);
foreach ($migration->getSql() as $sqlQuery) {
$this->addSql($sqlQuery);
}
foreach ($this->schemaProvider->getSqlDiffToMigrate($fromSchema, $toSchema) as $sql) {
$this->addSql(new Query($sql));
}
$migration->freeze();
if (count($this->sql) !== 0) {
if (! $configuration->isDryRun()) {
$this->executeResult($configuration);
} else {
foreach ($this->sql as $query) {
$this->outputSqlQuery($query, $configuration);
}
}
} else {
$this->logger->warning('Migration {version} was executed but did not result in any SQL statements.', [
'version' => (string) $plan->getVersion(),
]);
}
$result->setState(State::POST);
$migration->{'post' . ucfirst($direction)}($toSchema);
$stopwatchEvent->stop();
$periods = $stopwatchEvent->getPeriods();
$lastPeriod = $periods[count($periods) - 1];
$result->setTime((float) $lastPeriod->getDuration() / 1000);
$result->setMemory($lastPeriod->getMemory());
$params = [
'version' => (string) $plan->getVersion(),
'time' => $lastPeriod->getDuration(),
'memory' => BytesFormatter::formatBytes($lastPeriod->getMemory()),
'direction' => $direction === Direction::UP ? 'migrated' : 'reverted',
];
$this->logger->info('Migration {version} {direction} (took {time}ms, used {memory} memory)', $params);
if (! $configuration->isDryRun()) {
$this->metadataStorage->complete($result);
} elseif (method_exists($this->metadataStorage, 'getSql')) {
foreach ($this->metadataStorage->getSql($result) as $sqlQuery) {
$this->addSql($sqlQuery);
}
}
if ($migration->isTransactional()) {
TransactionHelper::commitIfInTransaction($this->connection);
}
$plan->markAsExecuted($result);
$result->setState(State::NONE);
$this->dispatcher->dispatchVersionEvent(
Events::onMigrationsVersionExecuted,
$plan,
$configuration,
);
return $result;
}
/** @return mixed[] */
private function getMigrationHeader(MigrationPlan $planItem, AbstractMigration $migration, string $direction): array
{
$versionInfo = (string) $planItem->getVersion();
$description = $migration->getDescription();
if ($description !== '') {
$versionInfo .= ' (' . $description . ')';
}
$params = ['version_name' => $versionInfo];
if ($direction === Direction::UP) {
return ['++ migrating {version_name}', $params];
}
return ['++ reverting {version_name}', $params];
}
private function migrationEnd(Throwable $e, MigrationPlan $plan, ExecutionResult $result, MigratorConfiguration $configuration): void
{
$migration = $plan->getMigration();
if ($migration->isTransactional()) {
//only rollback transaction if in transactional mode
TransactionHelper::rollbackIfInTransaction($this->connection);
}
$plan->markAsExecuted($result);
$this->logResult($e, $result, $plan);
$this->dispatcher->dispatchVersionEvent(
Events::onMigrationsVersionSkipped,
$plan,
$configuration,
);
}
private function logResult(Throwable $e, ExecutionResult $result, MigrationPlan $plan): void
{
if ($result->isSkipped()) {
$this->logger->notice(
'Migration {version} skipped during {state}. Reason: "{reason}"',
[
'version' => (string) $plan->getVersion(),
'reason' => $e->getMessage(),
'state' => $this->getExecutionStateAsString($result->getState()),
],
);
} elseif ($result->hasError()) {
$this->logger->error(
'Migration {version} failed during {state}. Error: "{error}"',
[
'version' => (string) $plan->getVersion(),
'error' => $e->getMessage(),
'state' => $this->getExecutionStateAsString($result->getState()),
],
);
}
}
private function executeResult(MigratorConfiguration $configuration): void
{
foreach ($this->sql as $key => $query) {
$this->outputSqlQuery($query, $configuration);
$stopwatchEvent = $this->stopwatch->start('query');
// executeQuery() must be used here because $query might return a result set, for instance REPAIR does
$this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes());
$stopwatchEvent->stop();
if (! $configuration->getTimeAllQueries()) {
continue;
}
$this->logger->notice('Query took {duration}ms', [
'duration' => $stopwatchEvent->getDuration(),
]);
}
}
private function outputSqlQuery(Query $query, MigratorConfiguration $configuration): void
{
$params = $this->parameterFormatter->formatParameters(
$query->getParameters(),
$query->getTypes(),
);
$this->logger->log(
$configuration->getTimeAllQueries() ? LogLevel::NOTICE : LogLevel::DEBUG,
'{query} {params}',
[
'query' => $query->getStatement(),
'params' => $params,
],
);
}
private function getFromSchema(MigratorConfiguration $configuration): Schema
{
// if we're in a dry run, use the from Schema instead of reading the schema from the database
if ($configuration->isDryRun() && $configuration->getFromSchema() !== null) {
return $configuration->getFromSchema();
}
return $this->schemaProvider->createFromSchema();
}
private function getExecutionStateAsString(int $state): string
{
return match ($state) {
State::PRE => 'Pre-Checks',
State::POST => 'Post-Checks',
State::EXEC => 'Execution',
default => 'No State',
};
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\DBAL\Connection;
use Doctrine\Migrations\AbstractMigration;
use Psr\Log\LoggerInterface;
/**
* The DbalMigrationFactory class is responsible for creating instances of a migration class name for a DBAL connection.
*
* @internal
*/
final class DbalMigrationFactory implements MigrationFactory
{
public function __construct(
private readonly Connection $connection,
private readonly LoggerInterface $logger,
) {
}
public function createVersion(string $migrationClassName): AbstractMigration
{
return new $migrationClassName(
$this->connection,
$this->logger,
);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria;
use Doctrine\Migrations\Exception\NoMigrationsToExecute;
use Doctrine\Migrations\Exception\UnknownMigrationVersion;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use function substr;
/**
* The DefaultAliasResolver class is responsible for resolving aliases like first, current, etc. to the actual version number.
*
* @internal
*/
final class DefaultAliasResolver implements AliasResolver
{
private const ALIAS_FIRST = 'first';
private const ALIAS_CURRENT = 'current';
private const ALIAS_PREV = 'prev';
private const ALIAS_NEXT = 'next';
private const ALIAS_LATEST = 'latest';
public function __construct(
private readonly MigrationPlanCalculator $migrationPlanCalculator,
private readonly MetadataStorage $metadataStorage,
private readonly MigrationStatusCalculator $migrationStatusCalculator,
) {
}
/**
* Returns the version number from an alias.
*
* Supported aliases are:
*
* - first: The very first version before any migrations have been run.
* - current: The current version.
* - prev: The version prior to the current version.
* - next: The version following the current version.
* - latest: The latest available version.
*
* If an existing version number is specified, it is returned verbatimly.
*
* @throws NoMigrationsToExecute
* @throws UnknownMigrationVersion
* @throws NoMigrationsFoundWithCriteria
*/
public function resolveVersionAlias(string $alias): Version
{
$availableMigrations = $this->migrationPlanCalculator->getMigrations();
$executedMigrations = $this->metadataStorage->getExecutedMigrations();
switch ($alias) {
case self::ALIAS_FIRST:
case '0':
return new Version('0');
case self::ALIAS_CURRENT:
try {
return $executedMigrations->getLast()->getVersion();
} catch (NoMigrationsFoundWithCriteria) {
return new Version('0');
}
// no break because of return
case self::ALIAS_PREV:
try {
return $executedMigrations->getLast(-1)->getVersion();
} catch (NoMigrationsFoundWithCriteria) {
return new Version('0');
}
// no break because of return
case self::ALIAS_NEXT:
$newMigrations = $this->migrationStatusCalculator->getNewMigrations();
try {
return $newMigrations->getFirst()->getVersion();
} catch (NoMigrationsFoundWithCriteria $e) {
throw NoMigrationsToExecute::new($e);
}
// no break because of return
case self::ALIAS_LATEST:
try {
return $availableMigrations->getLast()->getVersion();
} catch (NoMigrationsFoundWithCriteria) {
return $this->resolveVersionAlias(self::ALIAS_CURRENT);
}
// no break because of return
default:
if ($availableMigrations->hasMigration(new Version($alias))) {
return $availableMigrations->getMigration(new Version($alias))->getVersion();
}
if (substr($alias, 0, 7) === self::ALIAS_CURRENT) {
$val = (int) substr($alias, 7);
$targetMigration = null;
if ($val > 0) {
$newMigrations = $this->migrationStatusCalculator->getNewMigrations();
return $newMigrations->getFirst($val - 1)->getVersion();
}
return $executedMigrations->getLast($val)->getVersion();
}
}
throw UnknownMigrationVersion::new($alias);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
/**
* The Direction class contains constants for the directions a migration can be executed.
*
* @internal
*/
final class Direction
{
public const UP = 'up';
public const DOWN = 'down';
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
@@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use DateTimeImmutable;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\Query\Query;
use RuntimeException;
use Throwable;
use function count;
/**
* The ExecutionResult class is responsible for storing the result of a migration version after it executes.
*
* @internal
*/
final class ExecutionResult
{
/** @var Query[] */
private array $sql = [];
/**
* Seconds
*/
private float|null $time = null;
private float|null $memory = null;
private bool $skipped = false;
private bool $error = false;
private Throwable|null $exception = null;
private int $state;
private Schema|null $toSchema = null;
public function __construct(
private readonly Version $version,
private readonly string $direction = Direction::UP,
private DateTimeImmutable|null $executedAt = null,
) {
}
public function getDirection(): string
{
return $this->direction;
}
public function getExecutedAt(): DateTimeImmutable|null
{
return $this->executedAt;
}
public function setExecutedAt(DateTimeImmutable $executedAt): void
{
$this->executedAt = $executedAt;
}
public function getVersion(): Version
{
return $this->version;
}
public function hasSql(): bool
{
return count($this->sql) !== 0;
}
/** @return Query[] */
public function getSql(): array
{
return $this->sql;
}
/** @param Query[] $sql */
public function setSql(array $sql): void
{
$this->sql = $sql;
}
public function getTime(): float|null
{
return $this->time;
}
public function setTime(float $time): void
{
$this->time = $time;
}
public function getMemory(): float|null
{
return $this->memory;
}
public function setMemory(float $memory): void
{
$this->memory = $memory;
}
public function setSkipped(bool $skipped): void
{
$this->skipped = $skipped;
}
public function isSkipped(): bool
{
return $this->skipped;
}
public function setError(bool $error, Throwable|null $exception = null): void
{
$this->error = $error;
$this->exception = $exception;
}
public function hasError(): bool
{
return $this->error;
}
public function getException(): Throwable|null
{
return $this->exception;
}
public function setToSchema(Schema $toSchema): void
{
$this->toSchema = $toSchema;
}
public function getToSchema(): Schema
{
if ($this->toSchema === null) {
throw new RuntimeException('Cannot call getToSchema() when toSchema is null.');
}
return $this->toSchema;
}
public function getState(): int
{
return $this->state;
}
public function setState(int $state): void
{
$this->state = $state;
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Metadata\MigrationPlan;
use Doctrine\Migrations\MigratorConfiguration;
use Doctrine\Migrations\Query\Query;
/**
* The Executor defines the interface used for adding sql for a migration and executing that sql.
*
* @internal
*/
interface Executor
{
public function addSql(Query $sqlQuery): void;
public function execute(MigrationPlan $plan, MigratorConfiguration $migratorConfiguration): ExecutionResult;
}
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\AbstractMigration;
/**
* The MigrationFactory is responsible for creating instances of the migration class name.
*/
interface MigrationFactory
{
public function createVersion(string $migrationClassName): AbstractMigration;
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\MigrationPlanList;
/**
* The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current
* version to another version.
*/
interface MigrationPlanCalculator
{
/** @param Version[] $versions */
public function getPlanForVersions(array $versions, string $direction): MigrationPlanList;
public function getPlanUntilVersion(Version $to): MigrationPlanList;
/**
* Returns a sorted list of migrations.
*/
public function getMigrations(): AvailableMigrationsList;
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
/**
* The MigrationStatusCalculator is responsible for calculating the current status of
* migrated and not available versions.
*/
interface MigrationStatusCalculator
{
public function getExecutedUnavailableMigrations(): ExecutedMigrationsList;
public function getNewMigrations(): AvailableMigrationsList;
}
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Doctrine\Migrations\Exception\MigrationClassNotFound;
use Doctrine\Migrations\Metadata;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
use Doctrine\Migrations\Metadata\MigrationPlan;
use Doctrine\Migrations\Metadata\MigrationPlanList;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\MigrationsRepository;
use function array_diff;
use function array_filter;
use function array_map;
use function array_reverse;
use function count;
use function in_array;
use function reset;
use function uasort;
/**
* The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current
* version to another version.
*
* @internal
*/
final class SortedMigrationPlanCalculator implements MigrationPlanCalculator
{
public function __construct(
private readonly MigrationsRepository $migrationRepository,
private readonly MetadataStorage $metadataStorage,
private readonly Comparator $sorter,
) {
}
/** @param Version[] $versions */
public function getPlanForVersions(array $versions, string $direction): MigrationPlanList
{
$migrationsToCheck = $this->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;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
/**
* The State class contains constants for the different states a migration can be in during execution.
*
* @internal
*/
final class State
{
public const NONE = 0;
public const PRE = 1;
public const EXEC = 2;
public const POST = 3;
public const STATES = [
self::NONE,
self::PRE,
self::EXEC,
self::POST,
];
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Migrations\Version;
use Stringable;
final class Version implements Stringable
{
public function __construct(private readonly string $version)
{
}
public function __toString(): string
{
return $this->version;
}
public function equals(mixed $object): bool
{
return $object instanceof self && $object->version === $this->version;
}
}