From bcbe04a07947b46edaaaf65421856c505c359d7a Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 19:51:47 +0200 Subject: [PATCH 1/7] !!! Migrate to doctrine/migration v3 way of creating the adaptor * Implement a DependencyFactory for TYPO3 * Get rid of DoctrineService * Get rid of overwriting the now "final class" *Command from doctrine:migrate * Wire the dependencies in Services.yaml * Get rid of obsolete Commands.php * Add new commands introduced with doctrine/migrations v3: - migrations:dump-schema - migrations:generate - migrations:latest - migrations:list - migrations:up-to-date !!! Compatibility Notes: * `--dry-run` is currently not supported anymore for the "AbstractDataHandlerMigration" * An upgrade in the doctrine_migrationstatus is required, see README.md --- Classes/Command/DiffCommand.php | 43 ---- Classes/Command/ExecuteCommand.php | 46 ----- Classes/Command/MigrateCommand.php | 46 ----- Classes/Command/RollupCommand.php | 43 ---- Classes/Command/StatusCommand.php | 43 ---- Classes/Command/UpToDateCommand.php | 43 ---- Classes/Command/VersionCommand.php | 43 ---- .../AbstractDataHandlerMigration.php | 36 +--- Classes/Service/DoctrineService.php | 183 ------------------ Classes/Typo3ConfigurationLoader.php | 95 +++++++++ Classes/Typo3DependencyFactory.php | 34 ++++ Configuration/Commands.php | 32 --- Configuration/Services.yaml | 87 +++++++-- README.md | 30 ++- ext_tables.sql | 5 +- 15 files changed, 221 insertions(+), 588 deletions(-) delete mode 100644 Classes/Command/DiffCommand.php delete mode 100644 Classes/Command/ExecuteCommand.php delete mode 100644 Classes/Command/MigrateCommand.php delete mode 100644 Classes/Command/RollupCommand.php delete mode 100644 Classes/Command/StatusCommand.php delete mode 100644 Classes/Command/UpToDateCommand.php delete mode 100644 Classes/Command/VersionCommand.php delete mode 100644 Classes/Service/DoctrineService.php create mode 100644 Classes/Typo3ConfigurationLoader.php create mode 100644 Classes/Typo3DependencyFactory.php delete mode 100644 Configuration/Commands.php diff --git a/Classes/Command/DiffCommand.php b/Classes/Command/DiffCommand.php deleted file mode 100644 index dd7e1d8..0000000 --- a/Classes/Command/DiffCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/ExecuteCommand.php b/Classes/Command/ExecuteCommand.php deleted file mode 100644 index 8258a14..0000000 --- a/Classes/Command/ExecuteCommand.php +++ /dev/null @@ -1,46 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $dryRun = (bool)$input->getOption('dry-run'); - $this->doctrineService->setDryRun($dryRun); - - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/MigrateCommand.php b/Classes/Command/MigrateCommand.php deleted file mode 100644 index 3b12447..0000000 --- a/Classes/Command/MigrateCommand.php +++ /dev/null @@ -1,46 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $dryRun = (bool)$input->getOption('dry-run'); - $this->doctrineService->setDryRun($dryRun); - - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/RollupCommand.php b/Classes/Command/RollupCommand.php deleted file mode 100644 index 1428de6..0000000 --- a/Classes/Command/RollupCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/StatusCommand.php b/Classes/Command/StatusCommand.php deleted file mode 100644 index fc1fbed..0000000 --- a/Classes/Command/StatusCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/UpToDateCommand.php b/Classes/Command/UpToDateCommand.php deleted file mode 100644 index d7632e6..0000000 --- a/Classes/Command/UpToDateCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Command/VersionCommand.php b/Classes/Command/VersionCommand.php deleted file mode 100644 index cd423ca..0000000 --- a/Classes/Command/VersionCommand.php +++ /dev/null @@ -1,43 +0,0 @@ -doctrineService = $doctrineService ?? GeneralUtility::makeInstance(DoctrineService::class); - parent::__construct(); - } - - protected function configure(): void - { - parent::configure(); - - $this->addOption( - 'connection', - null, - InputOption::VALUE_OPTIONAL, - 'The DB connection to use.' - ); - } - - public function initialize(InputInterface $input, OutputInterface $output): void - { - $connectionName = $input->getOption('connection') ?? 'Default'; - $this->configuration = $this->doctrineService->getMigrationConfiguration($connectionName); - - parent::initialize($input, $output); - } -} diff --git a/Classes/Migration/AbstractDataHandlerMigration.php b/Classes/Migration/AbstractDataHandlerMigration.php index 4b26349..3d2f401 100644 --- a/Classes/Migration/AbstractDataHandlerMigration.php +++ b/Classes/Migration/AbstractDataHandlerMigration.php @@ -4,20 +4,15 @@ namespace KayStrobach\Migrations\Migration; +use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -use Doctrine\Migrations\Version\Version; use KayStrobach\Migrations\DataHandling\DryRunDataHandler; use KayStrobach\Migrations\Service\DoctrineMigrationCoordinator; -use KayStrobach\Migrations\Service\DoctrineService; +use Psr\Log\LoggerInterface; use TYPO3\CMS\Core\Core\Bootstrap; -use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\DataHandling\DataHandler; -use TYPO3\CMS\Core\Log\LogManager; -use TYPO3\CMS\Core\Package\PackageManager; -use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\VersionNumberUtility; /** * Base class for migrations using the TYPO3 DataHandler @@ -40,31 +35,12 @@ abstract class AbstractDataHandlerMigration extends AbstractMigration private DoctrineMigrationCoordinator $doctrineMigrationCoordinator; - private DoctrineService $doctrineService; - - public function __construct(Version $version) + public function __construct(Connection $connection, LoggerInterface $logger) { - parent::__construct($version); + parent::__construct($connection, $logger); $this->doctrineMigrationCoordinator = new DoctrineMigrationCoordinator(); $this->doctrineMigrationCoordinator->setCurrentVersion(static::class); - - if (VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()) < 100000) { - $dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class); - $packageManager = GeneralUtility::makeInstance(PackageManager::class, $dependencyOrderingService); - } else { - $packageManager = GeneralUtility::makeInstance(PackageManager::class); - } - - // todo: refactor this with proper dependency injection if possible - /** @var DoctrineService $doctrineService */ - $doctrineService = GeneralUtility::makeInstance( - DoctrineService::class, - $packageManager, - GeneralUtility::makeInstance(ConnectionPool::class), - GeneralUtility::makeInstance(LogManager::class), - ); - $this->doctrineService = $doctrineService; } /** @@ -124,7 +100,9 @@ public function preDown(Schema $schema): void */ protected function getDataHandler(array $dataMap = [], array $commandMap = []): DataHandler { - $dataHandlerClass = $this->doctrineService->isDryRun() ? DryRunDataHandler::class : DataHandler::class; + // @todo Find a way to pass the information about --dry-run from the Command to here: + #$dataHandlerClass = $isDryRun ? DryRunDataHandler::class : DataHandler::class; + $dataHandlerClass = DataHandler::class; /** @var DataHandler $dataHandler */ $dataHandler = GeneralUtility::makeInstance($dataHandlerClass); diff --git a/Classes/Service/DoctrineService.php b/Classes/Service/DoctrineService.php deleted file mode 100644 index ccf9b56..0000000 --- a/Classes/Service/DoctrineService.php +++ /dev/null @@ -1,183 +0,0 @@ -packageManager = $packageManager ?? $this->getPackageManager(); - $this->connectionPool = $connectionPool ?? GeneralUtility::makeInstance(ConnectionPool::class); - $logManager = $logManager ?? GeneralUtility::makeInstance(LogManager::class); - $this->setLogger($logManager->getLogger(static::class)); - } - - /** - * Return the configuration needed for Migrations. - */ - public function getMigrationConfiguration(string $connectionName, string $filterForPackageKey = null): Configuration - { - $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); - - $configuration = new Configuration( - $connection, - $this->getOutputWriter() - ); - - $configuration->setMigrationsNamespace('KayStrobach\Migrations\Persistence\Doctrine\Migrations'); - $publicPath = Environment::getPublicPath(); - - GeneralUtility::mkdir_deep($publicPath . '/fileadmin/Migrations'); - $configuration->setMigrationsDirectory($publicPath . '/fileadmin/Migrations'); - - $configuration->setMigrationsTableName(self::MIGRATION_TABLE_NAME); - - $configuration->createMigrationTable(); - - $databasePlatformName = $connection->getDatabasePlatform()->getName(); - foreach ($this->packageManager->getActivePackages() as $package) { - if ($filterForPackageKey !== null && $package->getPackageKey() !== $filterForPackageKey) { - continue; - } - - [$namespace, $path] = $this->getPackageMigrationNamespaceAndDirectory($package); - - if ($namespace === null || $path === null) { - $this->debug(sprintf('Package %s does not contain any migrations', $package->getPackageKey())); - continue; - } - - $plattformPath = $path . ucfirst($databasePlatformName) . '/'; - - if (is_dir($path)) { - $configuration->setMigrationsNamespace($namespace); - - $configuration->registerMigrationsFromDirectory($path); - } - - if (is_dir($plattformPath)) { - $namespace .= ucfirst($databasePlatformName); - $configuration->setMigrationsNamespace($namespace); - $configuration->registerMigrationsFromDirectory($plattformPath); - if ($filterForPackageKey !== null) { - $configuration->setMigrationsDirectory($plattformPath); - } - } elseif ($connectionName === 'Default') { - $configuration->setMigrationsNamespace($namespace); - $configuration->registerMigrationsFromDirectory($path); - - if ($filterForPackageKey !== null) { - $configuration->setMigrationsDirectory($path); - } - } - } - return $configuration; - } - - private function getOutputWriter(): OutputWriter - { - $this->output = []; - return new OutputWriter( - function (string $message) { - $outputFormatter = new OutputFormatter(true); - echo $outputFormatter->format($message); - $this->debug($message); - $this->output[] = $message; - } - ); - } - - /** - * @return array{0: string, 1: string} [The namespace, the full package directory] - */ - private function getPackageMigrationNamespaceAndDirectory(PackageInterface $package): array - { - $autoloadComposerDefinition = $package->getValueFromComposerManifest('autoload'); - - if (($autoloadComposerDefinition->{'psr-4'} ?? null) instanceof \stdClass) { - $psr4Namespaces = get_object_vars($autoloadComposerDefinition->{'psr-4'}); - foreach ($psr4Namespaces as $namespace => $dir) { - if (strpos($namespace, '\\Migrations\\')) { - $fullDir = $package->getPackagePath() . $dir . '/'; - - return [$namespace, $fullDir]; - } - } - } - - return [null, null]; - } - - public function setDryRun(bool $dryRun): void - { - $this->dryRun = $dryRun; - } - - public function isDryRun(): bool - { - return $this->dryRun; - } - - public function getLogger(): ?LoggerInterface - { - return $this->logger; - } - - public function debug(string $message): void - { - if ($this->getLogger()) { - $this->getLogger()->debug($message); - } - } - - protected function getPackageManager(): PackageManager - { - if (VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()) < 100000) { - $dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class); - $packageManager = GeneralUtility::makeInstance(PackageManager::class, $dependencyOrderingService); - } else { - $packageManager = GeneralUtility::makeInstance(PackageManager::class); - } - - return $packageManager; - } -} diff --git a/Classes/Typo3ConfigurationLoader.php b/Classes/Typo3ConfigurationLoader.php new file mode 100644 index 0000000..ecfad80 --- /dev/null +++ b/Classes/Typo3ConfigurationLoader.php @@ -0,0 +1,95 @@ +packageManager = $packageManager; + $this->connectionPool = $connectionPool; + $this->setLogger($logManager->getLogger()); + } + + public function getConfiguration(): Configuration + { + $configuration = new Configuration(); + + GeneralUtility::mkdir_deep(Environment::getVarPath() . '/migrations'); + $configuration->addMigrationsDirectory( + 'KayStrobach\Migrations\Persistence\Doctrine\Migrations', + Environment::getVarPath() . '/migrations' + ); + + // Store in our own doctrine_migrationstatus table + $metadataStorageConfiguration = new TableMetadataStorageConfiguration(); + $metadataStorageConfiguration->setTableName(self::MIGRATION_TABLE_NAME); + $configuration->setMetadataStorageConfiguration($metadataStorageConfiguration); + + $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); + $databasePlatformName = $connection->getDatabasePlatform()->getName(); + + foreach ($this->packageManager->getActivePackages() as $package) { + [$namespace, $path] = $this->getPackageMigrationNamespaceAndDirectory($package); + + if ($namespace === null || $path === null) { + $this->logger->debug(sprintf('Package %s does not contain any migrations', $package->getPackageKey())); + continue; + } + + $plattformPath = $path . ucfirst($databasePlatformName) . '/'; + + if (is_dir($plattformPath)) { + $this->logger->debug(sprintf('Adding migrations for Package %s', $package->getPackageKey())); + $namespace .= ucfirst($databasePlatformName); + $configuration->addMigrationsDirectory($namespace, $plattformPath); + } + } + return $configuration; + } + + /** + * @return array{0: string|null, 1: string|null} [The namespace, the full package directory] + */ + private function getPackageMigrationNamespaceAndDirectory(PackageInterface $package): array + { + $autoloadComposerDefinition = $package->getValueFromComposerManifest('autoload'); + + if (($autoloadComposerDefinition->{'psr-4'} ?? null) instanceof \stdClass) { + $psr4Namespaces = get_object_vars($autoloadComposerDefinition->{'psr-4'}); + foreach ($psr4Namespaces as $namespace => $dir) { + if (strpos($namespace, '\\Migrations\\')) { + $fullDir = $package->getPackagePath() . $dir . '/'; + + return [$namespace, $fullDir]; + } + } + } + + return [null, null]; + } + +} diff --git a/Classes/Typo3DependencyFactory.php b/Classes/Typo3DependencyFactory.php new file mode 100644 index 0000000..cd61770 --- /dev/null +++ b/Classes/Typo3DependencyFactory.php @@ -0,0 +1,34 @@ +getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); + $connectionLoader = new ExistingConnection($connection); + + return DependencyFactory::fromConnection( + $configurationLoader, + $connectionLoader, + $logManager->getLogger() + ); + } + +} diff --git a/Configuration/Commands.php b/Configuration/Commands.php deleted file mode 100644 index fdcab8c..0000000 --- a/Configuration/Commands.php +++ /dev/null @@ -1,32 +0,0 @@ - [ - 'class' => \KayStrobach\Migrations\Command\DiffCommand::class, - 'schedulable' => false, - ], - 'migrations:execute' => [ - 'class' => \KayStrobach\Migrations\Command\ExecuteCommand::class, - 'schedulable' => false, - ], - 'migrations:migrate' => [ - 'class' => \KayStrobach\Migrations\Command\MigrateCommand::class, - 'schedulable' => false, - ], - 'migrations:rollup' => [ - 'class' => \KayStrobach\Migrations\Command\RollupCommand::class, - 'schedulable' => false, - ], - 'migrations:uptodate' => [ - 'class' => \KayStrobach\Migrations\Command\UpToDateCommand::class, - 'schedulable' => false, - ], - 'migrations:status' => [ - 'class' => \KayStrobach\Migrations\Command\StatusCommand::class, - 'schedulable' => false, - ], - 'migrations:version' => [ - 'class' => \KayStrobach\Migrations\Command\VersionCommand::class, - 'schedulable' => false, - ], -]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index c8ec0f2..0e30b8c 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -10,51 +10,106 @@ services: arguments: - '@Psr\Container\ContainerInterface' - KayStrobach\Migrations\Command\DiffCommand: + KayStrobach\Migrations\Typo3ConfigurationLoader: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@TYPO3\CMS\Core\Package\PackageManager' + - '@TYPO3\CMS\Core\Database\ConnectionPool' + - '@TYPO3\CMS\Core\Log\LogManager' + + KayStrobach\Migrations\Typo3DependencyFactory: + factory: [null, 'create'] + arguments: + - '@TYPO3\CMS\Core\Log\LogManager' + - '@TYPO3\CMS\Core\Database\ConnectionPool' + - '@KayStrobach\Migrations\Typo3ConfigurationLoader' + + Doctrine\Migrations\Tools\Console\Command\CurrentCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:current' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\DiffCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' command: 'migrations:diff' schedulable: false - KayStrobach\Migrations\Command\ExecuteCommand: + Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:dump-schema' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\ExecuteCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' command: 'migrations:execute' schedulable: false - KayStrobach\Migrations\Command\MigrateCommand: + Doctrine\Migrations\Tools\Console\Command\GenerateCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:generate' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\LatestCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:latest' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\ListCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:list' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\MigrateCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' command: 'migrations:migrate' schedulable: false - KayStrobach\Migrations\Command\RollupCommand: + Doctrine\Migrations\Tools\Console\Command\RollupCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' command: 'migrations:rollup' schedulable: false - KayStrobach\Migrations\Command\UpToDateCommand: + Doctrine\Migrations\Tools\Console\Command\StatusCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' - command: 'migrations:uptodate' + command: 'migrations:status' schedulable: false - KayStrobach\Migrations\Command\StatusCommand: + Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' - command: 'migrations:status' + command: 'migrations:sync-metadata-storage' + schedulable: false + Doctrine\Migrations\Tools\Console\Command\UpToDateCommand: + arguments: + - '@KayStrobach\Migrations\Typo3DependencyFactory' + tags: + - name: 'console.command' + command: 'migrations:up-to-date' schedulable: false - KayStrobach\Migrations\Command\VersionCommand: + Doctrine\Migrations\Tools\Console\Command\VersionCommand: arguments: - - '@KayStrobach\Migrations\Service\DoctrineService' + - '@KayStrobach\Migrations\Typo3DependencyFactory' tags: - name: 'console.command' command: 'migrations:version' diff --git a/README.md b/README.md index c9651c9..4cb0e82 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Migrate with doctrine migrations -__It's strongly recommend to use the "typo3" CLI binary!__ +This extension uses `doctrine/migrations` to migrate the database tables. + +__You should use the "typo3" CLI binary or "typo3cms" console and not `bin/doctrine-migrations`!__ To get the status of your migrations you can run: @@ -10,28 +12,18 @@ To execute all pending migrations you can run: /typo3 migrations:migrate -This will give you an output like this: +# Update from TYPO3 9/10 to 11/12 +Moving to TYPO3 11, this extensions switched from doctrine/migration 2.x to 3.x for compatibility +with PHP 8. + +After running the Database Compare migrations, apply the following migration to your migrations status table: ``` - == Configuration - >> Name: Doctrine Database Migrations - >> Database Driver: pdo_mysql - >> Database Name: myproject - >> Configuration Source: manually configured - >> Version Table Name: doctrine_migrationstatus - >> Migrations Namespace: KayStrobach\Migrations\Persistence\Doctrine\Migrations - >> Migrations Target Directory: /var/www/my-project//fileadmin/Migrations - >> Current Version: 0 - >> Latest Version: 2014-07-14 18:44:53 (20140714184453) - >> Executed Migrations: 0 - >> Available Migrations: 1 - >> New Migrations: 1 - - == Migration Versions - >> 2014-07-14 18:44:53 (20140714184453) migrations not migrated +SET @namespace = "KayStrobach\\Migrations\\Persistence\\Doctrine\\Migrations\\Version"; +UPDATE doctrine_migrationstatus SET version=concat(@namespace, version) WHERE version NOT LIKE concat(@namespace, '%'); ``` -This extension uses `doctrine/migrations` to migrate the database tables. +Replace the `@namespace` setting with the namespace of your extension. # Own migration diff --git a/ext_tables.sql b/ext_tables.sql index ba37757..9ac4188 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -10,6 +10,7 @@ CREATE TABLE tt_content CREATE TABLE doctrine_migrationstatus ( - version VARCHAR(14) NOT NULL PRIMARY KEY, - executed_at DATETIME NOT NULL + version VARCHAR(191) NOT NULL PRIMARY KEY, + executed_at DATETIME DEFAULT NULL, + execution_time INT(11) DEFAULT NULL, ); From 7beabee24f0d0a7642a56ea83083d673bb7d19a1 Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 19:52:11 +0200 Subject: [PATCH 2/7] Some adaptations to the tests to make static tests happy again --- Classes/Tester/DoctrineCommandRunner.php | 2 +- Tests/Functional/Command/MigrateCommandTest.php | 11 ++++++----- .../AbstractDataHandlerMigrationTest.php | 7 ++++--- phpstan-baseline.neon | 15 +++++---------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Classes/Tester/DoctrineCommandRunner.php b/Classes/Tester/DoctrineCommandRunner.php index 1bbe5ae..cf766d7 100644 --- a/Classes/Tester/DoctrineCommandRunner.php +++ b/Classes/Tester/DoctrineCommandRunner.php @@ -4,7 +4,7 @@ namespace KayStrobach\Migrations\Tester; -use KayStrobach\Migrations\Command\MigrateCommand; +use Doctrine\Migrations\Tools\Console\Command\MigrateCommand; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\QuestionHelper; diff --git a/Tests/Functional/Command/MigrateCommandTest.php b/Tests/Functional/Command/MigrateCommandTest.php index 02143e0..79f1cdb 100644 --- a/Tests/Functional/Command/MigrateCommandTest.php +++ b/Tests/Functional/Command/MigrateCommandTest.php @@ -4,8 +4,8 @@ namespace KayStrobach\Migrations\Tests\Functional\Command; -use KayStrobach\Migrations\Command\MigrateCommand; -use KayStrobach\Migrations\Service\DoctrineService; +use Doctrine\Migrations\Tools\Console\Command\MigrateCommand; +use KayStrobach\Migrations\Typo3ConfigurationLoader; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; @@ -13,7 +13,7 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; /** - * @covers \KayStrobach\Migrations\Command\MigrateCommand + * @covers \Doctrine\Migrations\Tools\Console\Command\MigrateCommand */ class MigrateCommandTest extends FunctionalTestCase { @@ -35,9 +35,10 @@ public function migrationCommandExecutesMigrationsDefinedInExtensionWhenMigratio $commandTester->assertCommandIsSuccessful(); + /** @var \TYPO3\CMS\Core\Database\Connection $connection */ $connection = $this->get(ConnectionPool::class) - ->getConnectionForTable(DoctrineService::MIGRATION_TABLE_NAME); - $result = $connection->select(['*'], DoctrineService::MIGRATION_TABLE_NAME)->fetchAllAssociative(); + ->getConnectionForTable(Typo3ConfigurationLoader::MIGRATION_TABLE_NAME); + $result = $connection->select(['*'], Typo3ConfigurationLoader::MIGRATION_TABLE_NAME)->fetchAllAssociative(); self::assertCount(1, $result); self::assertSame('20230804102700', $result[0]['version']); diff --git a/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php b/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php index 8fc455a..ab9dd39 100644 --- a/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php +++ b/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php @@ -4,8 +4,8 @@ namespace KayStrobach\Migrations\Tests\Functional\Migration; -use KayStrobach\Migrations\Service\DoctrineService; use KayStrobach\Migrations\Tester\DoctrineCommandRunner; +use KayStrobach\Migrations\Typo3ConfigurationLoader; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication; use TYPO3\CMS\Core\Core\Bootstrap; @@ -30,9 +30,10 @@ public function dataHandlerMigrationRunsDataHandler(): void $GLOBALS['BE_USER']->workspace = 0; $this->get(DoctrineCommandRunner::class)->executeMigrateCommand(); + /** @var \TYPO3\CMS\Core\Database\Connection $connection */ $connection = $this->get(ConnectionPool::class) - ->getConnectionForTable(DoctrineService::MIGRATION_TABLE_NAME); - $result = $connection->select(['*'], DoctrineService::MIGRATION_TABLE_NAME)->fetchAllAssociative(); + ->getConnectionForTable(Typo3ConfigurationLoader::MIGRATION_TABLE_NAME); + $result = $connection->select(['*'], Typo3ConfigurationLoader::MIGRATION_TABLE_NAME)->fetchAllAssociative(); self::assertCount(1, $result, 'No or more than one migration was executed'); self::assertSame('20230804162200', $result[0]['version']); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5c84ae8..be870a1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,16 +1,11 @@ parameters: ignoreErrors: - - message: "#^Method KayStrobach\\\\Migrations\\\\Service\\\\DoctrineService\\:\\:getPackageMigrationNamespaceAndDirectory\\(\\) should return array\\{string, string\\} but returns array\\{null, null\\}\\.$#" - count: 1 - path: Classes/Service/DoctrineService.php + message: "#^Call to an undefined method .*Statement::fetchAllAssociative#" + count: 2 + path: Tests/Functional/Command/MigrateCommandTest.php - - message: "#^Result of \\|\\| is always false\\.$#" + message: "#^Call to an undefined method .*Statement::fetchAllAssociative#" count: 1 - path: Classes/Service/DoctrineService.php - - - - message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" - count: 2 - path: Classes/Service/DoctrineService.php + path: Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php From 3268a3c5c465b77bdc8fef5e1889b8f56bc70685 Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 20:01:25 +0200 Subject: [PATCH 3/7] Fix code styling issues --- Classes/Migration/AbstractDataHandlerMigration.php | 2 +- Classes/Typo3ConfigurationLoader.php | 2 -- Classes/Typo3DependencyFactory.php | 5 +---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Classes/Migration/AbstractDataHandlerMigration.php b/Classes/Migration/AbstractDataHandlerMigration.php index 3d2f401..a1ab8bc 100644 --- a/Classes/Migration/AbstractDataHandlerMigration.php +++ b/Classes/Migration/AbstractDataHandlerMigration.php @@ -101,7 +101,7 @@ public function preDown(Schema $schema): void protected function getDataHandler(array $dataMap = [], array $commandMap = []): DataHandler { // @todo Find a way to pass the information about --dry-run from the Command to here: - #$dataHandlerClass = $isDryRun ? DryRunDataHandler::class : DataHandler::class; + //$dataHandlerClass = $isDryRun ? DryRunDataHandler::class : DataHandler::class; $dataHandlerClass = DataHandler::class; /** @var DataHandler $dataHandler */ diff --git a/Classes/Typo3ConfigurationLoader.php b/Classes/Typo3ConfigurationLoader.php index ecfad80..ec03a42 100644 --- a/Classes/Typo3ConfigurationLoader.php +++ b/Classes/Typo3ConfigurationLoader.php @@ -20,7 +20,6 @@ */ class Typo3ConfigurationLoader implements ConfigurationLoader, LoggerAwareInterface { - use LoggerAwareTrait; public const MIGRATION_TABLE_NAME = 'doctrine_migrationstatus'; @@ -91,5 +90,4 @@ private function getPackageMigrationNamespaceAndDirectory(PackageInterface $pack return [null, null]; } - } diff --git a/Classes/Typo3DependencyFactory.php b/Classes/Typo3DependencyFactory.php index cd61770..b9318b9 100644 --- a/Classes/Typo3DependencyFactory.php +++ b/Classes/Typo3DependencyFactory.php @@ -14,13 +14,11 @@ */ class Typo3DependencyFactory extends DependencyFactory { - public static function create( LogManager $logManager, ConnectionPool $connectionPool, Typo3ConfigurationLoader $configurationLoader - ): DependencyFactory - { + ): DependencyFactory { $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); $connectionLoader = new ExistingConnection($connection); @@ -30,5 +28,4 @@ public static function create( $logManager->getLogger() ); } - } From 1d970e645965cd7d80458a91faee48d40b461563 Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 20:03:15 +0200 Subject: [PATCH 4/7] Fix functional tests --- Tests/Functional/Command/MigrateCommandTest.php | 2 +- Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Functional/Command/MigrateCommandTest.php b/Tests/Functional/Command/MigrateCommandTest.php index 79f1cdb..f3aabaf 100644 --- a/Tests/Functional/Command/MigrateCommandTest.php +++ b/Tests/Functional/Command/MigrateCommandTest.php @@ -41,7 +41,7 @@ public function migrationCommandExecutesMigrationsDefinedInExtensionWhenMigratio $result = $connection->select(['*'], Typo3ConfigurationLoader::MIGRATION_TABLE_NAME)->fetchAllAssociative(); self::assertCount(1, $result); - self::assertSame('20230804102700', $result[0]['version']); + self::assertSame('KayStrobach\\Migrations\\TestFixtures\\Migrations\\Mysql\\Version20230804102700', $result[0]['version']); $result = $connection->select(['*'], 'pages', ['uid' => 1])->fetchAllAssociative(); self::assertSame('My test page', $result[0]['title']); diff --git a/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php b/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php index ab9dd39..5e1e256 100644 --- a/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php +++ b/Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php @@ -36,7 +36,7 @@ public function dataHandlerMigrationRunsDataHandler(): void $result = $connection->select(['*'], Typo3ConfigurationLoader::MIGRATION_TABLE_NAME)->fetchAllAssociative(); self::assertCount(1, $result, 'No or more than one migration was executed'); - self::assertSame('20230804162200', $result[0]['version']); + self::assertSame('KayStrobach\\Migrations\\TestFixtures\\Migrations\\Mysql\\Version20230804162200', $result[0]['version']); $result = BackendUtility::getRecord('pages', 1); self::assertIsArray($result); From 4b741772d8e9a37054c12c5a6c33be4284ad1561 Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 20:08:51 +0200 Subject: [PATCH 5/7] Fix styling and functional tests --- Classes/Typo3ConfigurationLoader.php | 4 ++-- composer.json | 2 +- phpstan-baseline.neon | 11 ----------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Classes/Typo3ConfigurationLoader.php b/Classes/Typo3ConfigurationLoader.php index ec03a42..e0d8220 100644 --- a/Classes/Typo3ConfigurationLoader.php +++ b/Classes/Typo3ConfigurationLoader.php @@ -55,14 +55,14 @@ public function getConfiguration(): Configuration [$namespace, $path] = $this->getPackageMigrationNamespaceAndDirectory($package); if ($namespace === null || $path === null) { - $this->logger->debug(sprintf('Package %s does not contain any migrations', $package->getPackageKey())); + $this->logger?->debug(sprintf('Package %s does not contain any migrations', $package->getPackageKey())); continue; } $plattformPath = $path . ucfirst($databasePlatformName) . '/'; if (is_dir($plattformPath)) { - $this->logger->debug(sprintf('Adding migrations for Package %s', $package->getPackageKey())); + $this->logger?->debug(sprintf('Adding migrations for Package %s', $package->getPackageKey())); $namespace .= ucfirst($databasePlatformName); $configuration->addMigrationsDirectory($namespace, $plattformPath); } diff --git a/composer.json b/composer.json index 6412371..8cc3a29 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "symfony/console": "^5.4 || ^6.2", "symfony/filesystem": "^5.4 || ^6.2", "typo3/coding-standards": "^0.5.4", - "typo3/testing-framework": "^7.0 || ^8.0" + "typo3/testing-framework": "^7.0.4 || ^8.0" }, "autoload": { "psr-4": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index be870a1..e69de29 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,11 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Call to an undefined method .*Statement::fetchAllAssociative#" - count: 2 - path: Tests/Functional/Command/MigrateCommandTest.php - - - - message: "#^Call to an undefined method .*Statement::fetchAllAssociative#" - count: 1 - path: Tests/Functional/Migration/AbstractDataHandlerMigrationTest.php From 2351052c02a0c524dbda2b009de1603f0d55349d Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Tue, 19 Sep 2023 21:14:15 +0200 Subject: [PATCH 6/7] Fix static php test with TYPO3 12 --- Classes/Migration/AbstractDataHandlerMigration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Migration/AbstractDataHandlerMigration.php b/Classes/Migration/AbstractDataHandlerMigration.php index a1ab8bc..673143b 100644 --- a/Classes/Migration/AbstractDataHandlerMigration.php +++ b/Classes/Migration/AbstractDataHandlerMigration.php @@ -77,7 +77,7 @@ public function preUp(Schema $schema): void $this->getMigrationCoordinator()->setCurrentVersion($version); - Bootstrap::initializeBackendAuthentication(true); + Bootstrap::initializeBackendAuthentication(); } public function postUp(Schema $schema): void @@ -89,7 +89,7 @@ public function postUp(Schema $schema): void public function preDown(Schema $schema): void { - Bootstrap::initializeBackendAuthentication(true); + Bootstrap::initializeBackendAuthentication(); } /** From ef0aef7ec056c8974b237643507b4e17833f18f2 Mon Sep 17 00:00:00 2001 From: Ernesto Baschny Date: Fri, 22 Sep 2023 13:38:22 +0200 Subject: [PATCH 7/7] Add an upgrade wizard to the doctrine_migrationstatus table To transform the version column from single version to fully qualified classname. See https://github.com/doctrine/migrations/blob/3.6.x/UPGRADE.md --- .../StatusTableUpgradeWizard.php | 144 ++++++++++++++++++ README.md | 8 +- composer.json | 2 +- ext_localconf.php | 3 + 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 Classes/UpgradeWizard/StatusTableUpgradeWizard.php diff --git a/Classes/UpgradeWizard/StatusTableUpgradeWizard.php b/Classes/UpgradeWizard/StatusTableUpgradeWizard.php new file mode 100644 index 0000000..254341e --- /dev/null +++ b/Classes/UpgradeWizard/StatusTableUpgradeWizard.php @@ -0,0 +1,144 @@ + + * @throws \Doctrine\DBAL\Exception + */ + private function getVersionMapping(): array + { + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $connection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); + $databasePlatformName = $connection->getDatabasePlatform()->getName(); + + $packageManager = GeneralUtility::makeInstance(PackageManager::class); + $mapping = []; + foreach ($packageManager->getActivePackages() as $package) { + $autoloadComposerDefinition = $package->getValueFromComposerManifest('autoload'); + if (! ($autoloadComposerDefinition->{'psr-4'} ?? null) instanceof \stdClass) { + continue; + } + $psr4Namespaces = get_object_vars($autoloadComposerDefinition->{'psr-4'}); + foreach ($psr4Namespaces as $namespace => $dir) { + if (strpos($namespace, '\\Migrations')) { + $fullDir = $package->getPackagePath() . $dir . ucfirst($databasePlatformName); + $files = glob($fullDir . '/Version*.php'); + if (! is_array($files)) { + continue; + } + foreach ($files as $file) { + $className = rtrim($namespace, '\\') . '\\' . ucfirst($databasePlatformName) + . '\\' . str_replace('.php', '', basename($file)); + $legacyNumber = preg_replace('/[^0-9]/', '', basename($file)); + $mapping[$legacyNumber] = $className; + } + } + } + } + return $mapping; + } + + private function getQueryBuilder(): QueryBuilder + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableName); + return $connection->createQueryBuilder(); + } + + /** + * Loop through all available migration classes and update the status table accordingly + * + * @inheritDoc + */ + public function executeUpdate(): bool + { + $queryBuilder = $this->getQueryBuilder(); + foreach ($this->getVersionMapping() as $version => $className) { + $queryBuilder->update($this->tableName) + ->set('version', $className, true, Connection::PARAM_STR) + ->where( + $queryBuilder->expr()->eq( + 'version', + $queryBuilder->createNamedParameter($version, Connection::PARAM_STR) + ) + ); + $queryBuilder->execute(); + } + + return true; + } + + /** + * Check if there is any version stored in the status table which does not contain a "\" + * + * @inheritDoc + */ + public function updateNecessary(): bool + { + $queryBuilder = $this->getQueryBuilder(); + $count = (int)$queryBuilder->count('*') + ->from($this->tableName) + ->where($queryBuilder->expr()->notLike( + 'version', + $queryBuilder->createNamedParameter('%\\\%', Connection::PARAM_STR) + )) + ->executeQuery() + ->fetchOne(); + return $count > 0; + } + + /** + * @inheritDoc + */ + public function getPrerequisites(): array + { + return [DatabaseUpdatedPrerequisite::class]; + } +} diff --git a/README.md b/README.md index 4cb0e82..515e858 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ To execute all pending migrations you can run: /typo3 migrations:migrate -# Update from TYPO3 9/10 to 11/12 +# Upgrade from TYPO3 9/10 to 11/12 Moving to TYPO3 11, this extensions switched from doctrine/migration 2.x to 3.x for compatibility with PHP 8. -After running the Database Compare migrations, apply the following migration to your migrations status table: +After running the Database Compare migrations, don't forget to run the Upgrade Wizards which will +migrate the table `migration_statustable`: ``` -SET @namespace = "KayStrobach\\Migrations\\Persistence\\Doctrine\\Migrations\\Version"; -UPDATE doctrine_migrationstatus SET version=concat(@namespace, version) WHERE version NOT LIKE concat(@namespace, '%'); +bin/typo3 upgrade:run migration_statustable ``` Replace the `@namespace` setting with the namespace of your extension. diff --git a/composer.json b/composer.json index 8cc3a29..c22322e 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,7 @@ "scripts": { "cs:check": "php-cs-fixer fix --config .php-cs-fixer.php -v --dry-run --diff", "cs:fix": "php-cs-fixer fix --config .php-cs-fixer.php -v --diff", - "php:static": "phpstan analyze --no-interaction", + "php:static": "phpstan analyze --no-interaction --memory-limit=1G", "test:php:functional": "phpunit -c ./Build/FunctionalTests.xml" } } diff --git a/ext_localconf.php b/ext_localconf.php index 7be3ada..91b078f 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -2,3 +2,6 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = \KayStrobach\Migrations\Hooks\DataHandlerVersionAddHook::class; + +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['migration_statustable'] + = \KayStrobach\Migrations\UpgradeWizard\StatusTableUpgradeWizard::class;