vendor/doctrine/migrations/lib/Doctrine/Migrations/Metadata/Storage/TableMetadataStorage.php line 185

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\Migrations\Metadata\Storage;
  4. use DateTimeImmutable;
  5. use Doctrine\DBAL\Connection;
  6. use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
  7. use Doctrine\DBAL\Platforms\AbstractPlatform;
  8. use Doctrine\DBAL\Schema\AbstractSchemaManager;
  9. use Doctrine\DBAL\Schema\Table;
  10. use Doctrine\DBAL\Schema\TableDiff;
  11. use Doctrine\DBAL\Types\Types;
  12. use Doctrine\Migrations\Exception\MetadataStorageError;
  13. use Doctrine\Migrations\Metadata\AvailableMigration;
  14. use Doctrine\Migrations\Metadata\ExecutedMigration;
  15. use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
  16. use Doctrine\Migrations\MigrationsRepository;
  17. use Doctrine\Migrations\Version\Comparator as MigrationsComparator;
  18. use Doctrine\Migrations\Version\Direction;
  19. use Doctrine\Migrations\Version\ExecutionResult;
  20. use Doctrine\Migrations\Version\Version;
  21. use InvalidArgumentException;
  22. use function array_change_key_case;
  23. use function floatval;
  24. use function round;
  25. use function sprintf;
  26. use function strlen;
  27. use function strpos;
  28. use function strtolower;
  29. use function uasort;
  30. use const CASE_LOWER;
  31. final class TableMetadataStorage implements MetadataStorage
  32. {
  33.     private bool $isInitialized false;
  34.     private bool $schemaUpToDate false;
  35.     private Connection $connection;
  36.     /** @var AbstractSchemaManager<AbstractPlatform> */
  37.     private AbstractSchemaManager $schemaManager;
  38.     private AbstractPlatform $platform;
  39.     private TableMetadataStorageConfiguration $configuration;
  40.     private ?MigrationsRepository $migrationRepository null;
  41.     private MigrationsComparator $comparator;
  42.     public function __construct(
  43.         Connection $connection,
  44.         MigrationsComparator $comparator,
  45.         ?MetadataStorageConfiguration $configuration null,
  46.         ?MigrationsRepository $migrationRepository null
  47.     ) {
  48.         $this->migrationRepository $migrationRepository;
  49.         $this->connection          $connection;
  50.         $this->schemaManager       $connection->createSchemaManager();
  51.         $this->platform            $connection->getDatabasePlatform();
  52.         if ($configuration !== null && ! ($configuration instanceof TableMetadataStorageConfiguration)) {
  53.             throw new InvalidArgumentException(sprintf('%s accepts only %s as configuration'self::class, TableMetadataStorageConfiguration::class));
  54.         }
  55.         $this->configuration $configuration ?? new TableMetadataStorageConfiguration();
  56.         $this->comparator    $comparator;
  57.     }
  58.     public function getExecutedMigrations(): ExecutedMigrationsList
  59.     {
  60.         if (! $this->isInitialized()) {
  61.             return new ExecutedMigrationsList([]);
  62.         }
  63.         $this->checkInitialization();
  64.         $rows $this->connection->fetchAllAssociative(sprintf('SELECT * FROM %s'$this->configuration->getTableName()));
  65.         $migrations = [];
  66.         foreach ($rows as $row) {
  67.             $row array_change_key_case($rowCASE_LOWER);
  68.             $version = new Version($row[strtolower($this->configuration->getVersionColumnName())]);
  69.             $executedAt $row[strtolower($this->configuration->getExecutedAtColumnName())] ?? '';
  70.             $executedAt $executedAt !== ''
  71.                 DateTimeImmutable::createFromFormat($this->platform->getDateTimeFormatString(), $executedAt)
  72.                 : null;
  73.             $executionTime = isset($row[strtolower($this->configuration->getExecutionTimeColumnName())])
  74.                 ? floatval($row[strtolower($this->configuration->getExecutionTimeColumnName())] / 1000)
  75.                 : null;
  76.             $migration = new ExecutedMigration(
  77.                 $version,
  78.                 $executedAt instanceof DateTimeImmutable $executedAt null,
  79.                 $executionTime
  80.             );
  81.             $migrations[(string) $version] = $migration;
  82.         }
  83.         uasort($migrations, function (ExecutedMigration $aExecutedMigration $b): int {
  84.             return $this->comparator->compare($a->getVersion(), $b->getVersion());
  85.         });
  86.         return new ExecutedMigrationsList($migrations);
  87.     }
  88.     public function reset(): void
  89.     {
  90.         $this->checkInitialization();
  91.         $this->connection->executeStatement(
  92.             sprintf(
  93.                 'DELETE FROM %s WHERE 1 = 1',
  94.                 $this->platform->quoteIdentifier($this->configuration->getTableName())
  95.             )
  96.         );
  97.     }
  98.     public function complete(ExecutionResult $result): void
  99.     {
  100.         $this->checkInitialization();
  101.         if ($result->getDirection() === Direction::DOWN) {
  102.             $this->connection->delete($this->configuration->getTableName(), [
  103.                 $this->configuration->getVersionColumnName() => (string) $result->getVersion(),
  104.             ]);
  105.         } else {
  106.             $this->connection->insert($this->configuration->getTableName(), [
  107.                 $this->configuration->getVersionColumnName() => (string) $result->getVersion(),
  108.                 $this->configuration->getExecutedAtColumnName() => $result->getExecutedAt(),
  109.                 $this->configuration->getExecutionTimeColumnName() => $result->getTime() === null null : (int) round($result->getTime() * 1000),
  110.             ], [
  111.                 Types::STRING,
  112.                 Types::DATETIME_MUTABLE,
  113.                 Types::INTEGER,
  114.             ]);
  115.         }
  116.     }
  117.     public function ensureInitialized(): void
  118.     {
  119.         if (! $this->isInitialized()) {
  120.             $expectedSchemaChangelog $this->getExpectedTable();
  121.             $this->schemaManager->createTable($expectedSchemaChangelog);
  122.             $this->schemaUpToDate true;
  123.             $this->isInitialized  true;
  124.             return;
  125.         }
  126.         $this->isInitialized     true;
  127.         $expectedSchemaChangelog $this->getExpectedTable();
  128.         $diff                    $this->needsUpdate($expectedSchemaChangelog);
  129.         if ($diff === null) {
  130.             $this->schemaUpToDate true;
  131.             return;
  132.         }
  133.         $this->schemaUpToDate true;
  134.         $this->schemaManager->alterTable($diff);
  135.         $this->updateMigratedVersionsFromV1orV2toV3();
  136.     }
  137.     private function needsUpdate(Table $expectedTable): ?TableDiff
  138.     {
  139.         if ($this->schemaUpToDate) {
  140.             return null;
  141.         }
  142.         $currentTable $this->schemaManager->listTableDetails($this->configuration->getTableName());
  143.         $diff         $this->schemaManager->createComparator()->diffTable($currentTable$expectedTable);
  144.         return $diff instanceof TableDiff $diff null;
  145.     }
  146.     private function isInitialized(): bool
  147.     {
  148.         if ($this->isInitialized) {
  149.             return $this->isInitialized;
  150.         }
  151.         if ($this->connection instanceof PrimaryReadReplicaConnection) {
  152.             $this->connection->ensureConnectedToPrimary();
  153.         }
  154.         return $this->schemaManager->tablesExist([$this->configuration->getTableName()]);
  155.     }
  156.     private function checkInitialization(): void
  157.     {
  158.         if (! $this->isInitialized()) {
  159.             throw MetadataStorageError::notInitialized();
  160.         }
  161.         $expectedTable $this->getExpectedTable();
  162.         if ($this->needsUpdate($expectedTable) !== null) {
  163.             throw MetadataStorageError::notUpToDate();
  164.         }
  165.     }
  166.     private function getExpectedTable(): Table
  167.     {
  168.         $schemaChangelog = new Table($this->configuration->getTableName());
  169.         $schemaChangelog->addColumn(
  170.             $this->configuration->getVersionColumnName(),
  171.             'string',
  172.             ['notnull' => true'length' => $this->configuration->getVersionColumnLength()]
  173.         );
  174.         $schemaChangelog->addColumn($this->configuration->getExecutedAtColumnName(), 'datetime', ['notnull' => false]);
  175.         $schemaChangelog->addColumn($this->configuration->getExecutionTimeColumnName(), 'integer', ['notnull' => false]);
  176.         $schemaChangelog->setPrimaryKey([$this->configuration->getVersionColumnName()]);
  177.         return $schemaChangelog;
  178.     }
  179.     private function updateMigratedVersionsFromV1orV2toV3(): void
  180.     {
  181.         if ($this->migrationRepository === null) {
  182.             return;
  183.         }
  184.         $availableMigrations $this->migrationRepository->getMigrations()->getItems();
  185.         $executedMigrations  $this->getExecutedMigrations()->getItems();
  186.         foreach ($availableMigrations as $availableMigration) {
  187.             foreach ($executedMigrations as $k => $executedMigration) {
  188.                 if ($this->isAlreadyV3Format($availableMigration$executedMigration)) {
  189.                     continue;
  190.                 }
  191.                 $this->connection->update(
  192.                     $this->configuration->getTableName(),
  193.                     [
  194.                         $this->configuration->getVersionColumnName() => (string) $availableMigration->getVersion(),
  195.                     ],
  196.                     [
  197.                         $this->configuration->getVersionColumnName() => (string) $executedMigration->getVersion(),
  198.                     ]
  199.                 );
  200.                 unset($executedMigrations[$k]);
  201.             }
  202.         }
  203.     }
  204.     private function isAlreadyV3Format(AvailableMigration $availableMigrationExecutedMigration $executedMigration): bool
  205.     {
  206.         return strpos(
  207.             (string) $availableMigration->getVersion(),
  208.             (string) $executedMigration->getVersion()
  209.         ) !== strlen((string) $availableMigration->getVersion()) -
  210.                 strlen((string) $executedMigration->getVersion());
  211.     }
  212. }