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

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