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

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