From 82dffeb00b68845acdd84b851ccc65979be7d9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Poirier=20Th=C3=A9or=C3=AAt?= Date: Mon, 30 Sep 2024 13:15:43 -0400 Subject: [PATCH] [EntityMigrator/SonataIntegrationBundle] Refactor entity migrator --- .../UserSetCommentNullMigration.php | 6 +- .../BatchPrepareMigrationInterface.php | 15 -- .../entity-migrator/Command/BaseCommand.php | 97 -------- .../Command/MigrateCommand.php | 90 ++++---- .../Command/QueueBatchCommand.php | 165 -------------- .../entity-migrator/Command/SetupCommand.php | 3 +- .../EntityMigratorIntegration.php | 136 +++++++----- .../Entity/BaseEntityMigration.php | 29 +-- packages/entity-migrator/Entity/Migration.php | 3 +- ...istener.php => EntityWorkflowListener.php} | 44 ++-- .../MigrationWorkflowListener.php | 207 ++++++++++++++++++ .../Message/MigrateEntityCommand.php | 12 +- .../entity-migrator/MigrationInterface.php | 7 + packages/entity-migrator/Migrator.php | 52 +++-- .../Message/MigrateEntityCommandTest.php | 13 -- .../Workflow/EntityMigrationWorkflow.php | 71 ++++++ .../Workflow/MigrationWorkflow.php | 61 ++++++ .../Action}/WorkflowTransitionAction.php | 4 +- .../WorkflowTransitionAdminAction.php | 20 ++ .../Extension/WorkflowExtension.php | 14 +- .../DependencyInjection/ConfigurationTest.php | 3 + .../DependencyInjection/Configuration.php | 1 + .../DrawSonataIntegrationExtension.php | 3 +- .../Admin/BaseEntityMigrationAdmin.php | 6 +- .../DrawEntityMigratorAdmin.en.yaml | 2 +- .../DependencyInjection/ConfigurationTest.php | 2 +- .../Command/MigrateCommandTest.php | 53 +++++ .../event_dispatcher.xml | 37 +++- 28 files changed, 656 insertions(+), 500 deletions(-) delete mode 100644 packages/entity-migrator/BatchPrepareMigrationInterface.php delete mode 100644 packages/entity-migrator/Command/BaseCommand.php delete mode 100644 packages/entity-migrator/Command/QueueBatchCommand.php rename packages/entity-migrator/EventListener/{WorkflowListener.php => EntityWorkflowListener.php} (60%) create mode 100644 packages/entity-migrator/EventListener/MigrationWorkflowListener.php create mode 100644 packages/entity-migrator/Workflow/EntityMigrationWorkflow.php create mode 100644 packages/entity-migrator/Workflow/MigrationWorkflow.php rename packages/sonata-extra-bundle/{Controller => ActionableAdmin/Action}/WorkflowTransitionAction.php (92%) create mode 100644 packages/sonata-extra-bundle/ActionableAdmin/AdminAction/WorkflowTransitionAdminAction.php create mode 100644 tests/EntityMigrator/Command/MigrateCommandTest.php diff --git a/app/src/EntityMigration/UserSetCommentNullMigration.php b/app/src/EntityMigration/UserSetCommentNullMigration.php index 67d597bfc..39b82300e 100644 --- a/app/src/EntityMigration/UserSetCommentNullMigration.php +++ b/app/src/EntityMigration/UserSetCommentNullMigration.php @@ -7,13 +7,13 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; -use Draw\Component\EntityMigrator\BatchPrepareMigrationInterface; +use Draw\Component\EntityMigrator\MigrationInterface; use Draw\Component\EntityMigrator\MigrationTargetEntityInterface; /** - * @template-implements BatchPrepareMigrationInterface + * @template-implements MigrationInterface */ -class UserSetCommentNullMigration implements BatchPrepareMigrationInterface +class UserSetCommentNullMigration implements MigrationInterface { public static function getName(): string { diff --git a/packages/entity-migrator/BatchPrepareMigrationInterface.php b/packages/entity-migrator/BatchPrepareMigrationInterface.php deleted file mode 100644 index 7078edcea..000000000 --- a/packages/entity-migrator/BatchPrepareMigrationInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ -interface BatchPrepareMigrationInterface extends MigrationInterface -{ - public function createSelectIdQueryBuilder(): QueryBuilder; -} diff --git a/packages/entity-migrator/Command/BaseCommand.php b/packages/entity-migrator/Command/BaseCommand.php deleted file mode 100644 index 30d6d73af..000000000 --- a/packages/entity-migrator/Command/BaseCommand.php +++ /dev/null @@ -1,97 +0,0 @@ -getArgument('migration-name')) { - $io->block( - 'Which migration ?', - null, - 'fg=white;bg=blue', - ' ', - true - ); - - $question = new ChoiceQuestion( - 'Select which migration', - array_map( - static fn (Migration $migration) => $migration->getName(), - $this->managerRegistry->getRepository(Migration::class)->findAll() - ) - ); - - $input->setArgument('migration-name', $io->askQuestion($question)); - } - } - - protected function getMigrationName(InputInterface $input, OutputInterface $output): string - { - $io = new SymfonyStyle($input, $output); - - $migrationName = $input->getArgument('migration-name'); - - $migrationNames = array_map( - static fn (Migration $migration) => $migration->getName(), - $this->managerRegistry - ->getRepository(Migration::class) - ->findAll(), - ); - - sort($migrationNames); - - if (null !== $migrationName) { - if (\in_array($migrationName, $migrationNames, true)) { - return $migrationName; - } - - $io->warning(\sprintf('Migration [%s] is invalid.', $migrationName)); - - $migrationName = null; - } - - if (null === $migrationName) { - $helper = $this->getHelper('question'); - - \assert($helper instanceof QuestionHelper); - - $question = new ChoiceQuestion( - 'Which migration you want to execute?', - $migrationNames, - ); - - $question->setErrorMessage(\sprintf('Migration [%s] is invalid.', $migrationName)); - - $migrationName = $helper->ask($input, $output, $question); - } - - $io->note(\sprintf('Using migration [%s]', $migrationName)); - - return $migrationName; - } -} diff --git a/packages/entity-migrator/Command/MigrateCommand.php b/packages/entity-migrator/Command/MigrateCommand.php index 7e0a75169..b57791f60 100644 --- a/packages/entity-migrator/Command/MigrateCommand.php +++ b/packages/entity-migrator/Command/MigrateCommand.php @@ -2,21 +2,31 @@ namespace Draw\Component\EntityMigrator\Command; -use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; use Draw\Component\EntityMigrator\Entity\Migration; +use Draw\Component\EntityMigrator\Workflow\MigrationWorkflow; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Workflow\Registry; #[AsCommand( name: 'draw:entity-migrator:migrate', description: 'Migrate all entities', )] -class MigrateCommand extends BaseCommand +class MigrateCommand extends Command { + public function __construct( + private Registry $workflowRegistry, + private ManagerRegistry $managerRegistry, + ) { + parent::__construct(); + } + protected function configure(): void { $this @@ -24,6 +34,31 @@ protected function configure(): void ; } + protected function interact(InputInterface $input, OutputInterface $output): void + { + $io = new SymfonyStyle($input, $output); + + if (!$input->getArgument('migration-name')) { + $io->block( + 'Which migration ?', + null, + 'fg=white;bg=blue', + ' ', + true + ); + + $question = new ChoiceQuestion( + 'Select which migration', + array_map( + static fn (Migration $migration) => $migration->getName(), + $this->managerRegistry->getRepository(Migration::class)->findAll() + ) + ); + + $input->setArgument('migration-name', $io->askQuestion($question)); + } + } + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle( @@ -31,55 +66,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output ); - $migration = $this->migrator->getMigration($this->getMigrationName($input, $output)); - - $count = $migration->countAllThatNeedMigration(); - - if (0 === $count) { - $io->warning('No entity need migration'); - - return Command::SUCCESS; - } - $migrationEntity = $this->managerRegistry ->getRepository(Migration::class) - ->findOneBy(['name' => $migration::getName()]) + ->findOneBy(['name' => $input->getArgument('migration-name')]) ; - $manager = $this->managerRegistry->getManagerForClass(Migration::class); + $workflow = $this->workflowRegistry->get($migrationEntity, MigrationWorkflow::NAME); - \assert($manager instanceof EntityManagerInterface); - - $realCount = 0; - - $progress = $io->createProgressBar($count ?? 0); - $progress->setFormat(ProgressBar::FORMAT_DEBUG); - foreach ($migration->findAllThatNeedMigration() as $entity) { - $entityMigration = $this->entityMigrationRepository->load( - $entity, - $manager->getReference(Migration::class, $migrationEntity->getId()) - ); + $blockerList = $workflow->buildTransitionBlockerList($migrationEntity, MigrationWorkflow::TRANSITION_PROCESS); - $this->migrator->migrate($entityMigration); + if (!$blockerList->isEmpty()) { + foreach ($blockerList as $blocker) { + $io->warning('Process is blocked: '.$blocker->getMessage()); + } - ++$realCount; - - $progress->advance(); - - $manager->clear(); - - $this->servicesResetter?->reset(); + return Command::FAILURE; } - $progress->finish(); + $progressBar = $io->createProgressBar(); + $progressBar->setFormat(ProgressBar::FORMAT_DEBUG); - $io->newLine(); + $workflow->apply($migrationEntity, MigrationWorkflow::TRANSITION_PROCESS, ['progressBar' => $progressBar]); - $io->success(\sprintf( - 'Migration %s processed for %d entities', - $migration::getName(), - $realCount - )); + $io->newLine(); + $io->success('Migration started'); return Command::SUCCESS; } diff --git a/packages/entity-migrator/Command/QueueBatchCommand.php b/packages/entity-migrator/Command/QueueBatchCommand.php deleted file mode 100644 index c0e66aeea..000000000 --- a/packages/entity-migrator/Command/QueueBatchCommand.php +++ /dev/null @@ -1,165 +0,0 @@ -addArgument('migration-name', null, 'The migration name to migrate') - ; - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle( - $input, - $output - ); - - $manager = $this->managerRegistry->getManagerForClass(Migration::class); - - \assert($manager instanceof EntityManagerInterface); - - $migration = $this->migrator->getMigration($this->getMigrationName($input, $output)); - - if (!$migration instanceof BatchPrepareMigrationInterface) { - $io->error(\sprintf( - 'Migration %s does not implement %s', - $migration::getName(), - BatchPrepareMigrationInterface::class - )); - - return Command::FAILURE; - } - - $migrationEntity = $manager - ->getRepository(Migration::class) - ->findOneBy(['name' => $migration::getName()]) - ; - - $metadata = $manager - ->getMetadataFactory() - ->getMetadataFor($migration::getTargetEntityClass()) - ; - - $entityMigrationClass = $metadata->getReflectionClass() - ->getMethod('getEntityMigrationClass') - ->invoke(null) - ; - - $entityMigrationMetadata = $manager->getClassMetadata($entityMigrationClass); - - $queryBuilder = $migration->createSelectIdQueryBuilder(); - - $sql = $queryBuilder - ->addSelect( - $queryBuilder->expr()->literal($migrationEntity->getId()).' as migration_id', - $queryBuilder->expr()->literal('{}').' as transition_logs', - $queryBuilder->expr()->literal(date('Y-m-d H:i:s')).' as created_at', - ) - ->getQuery() - ->getSQL() - ; - - $manager - ->getConnection() - ->executeStatement( - \sprintf( - 'INSERT IGNORE INTO `%s` (entity_id, migration_id, transition_logs, created_at) %s', - $entityMigrationMetadata->getTableName(), - $sql - ), - array_map( - static fn (Parameter $parameter) => $parameter->getValue(), - $queryBuilder->getParameters()->toArray() - ) - ) - ; - - $queryBuilder = $manager - ->createQueryBuilder() - ->from($entityMigrationClass, 'entity_migration') - ->andWhere('entity_migration.state = :state') - ->setParameter('state', BaseEntityMigration::STATE_NEW) - ; - - $count = (int) (clone $queryBuilder) - ->select('count(entity_migration.id)') - ->getQuery() - ->getSingleScalarResult() - ; - - if (0 === $count) { - $io->warning('No new entity need migration'); - - return Command::SUCCESS; - } - - $result = $queryBuilder - ->select('entity_migration.id as id') - ->getQuery() - ->toIterable() - ; - - $progress = $io->createProgressBar($count); - $progress->setFormat(ProgressBar::FORMAT_DEBUG); - foreach ($result as $row) { - $migrationEntity = $manager->getReference($entityMigrationClass, $row['id']); - - $this->messageBus->dispatch( - new MigrateEntityCommand($migrationEntity) - ); - - $manager->clear(); - - $progress->advance(); - $this->servicesResetter?->reset(); - } - - $progress->finish(); - - $io->newLine(); - - $io->success(\sprintf( - 'Migration %s queued for %d entities', - $migration::getName(), - $count - )); - - return Command::SUCCESS; - } -} diff --git a/packages/entity-migrator/Command/SetupCommand.php b/packages/entity-migrator/Command/SetupCommand.php index 459f4f1e7..aa174bb46 100644 --- a/packages/entity-migrator/Command/SetupCommand.php +++ b/packages/entity-migrator/Command/SetupCommand.php @@ -5,6 +5,7 @@ use Doctrine\Persistence\ManagerRegistry; use Draw\Component\EntityMigrator\Entity\Migration; use Draw\Component\EntityMigrator\Migrator; +use Draw\Component\EntityMigrator\Workflow\MigrationWorkflow; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -40,7 +41,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manager->persist( (new Migration()) ->setName($name) - ->setState('new') + ->setState(MigrationWorkflow::PLACE_NEW) ); $manager->flush(); diff --git a/packages/entity-migrator/DependencyInjection/EntityMigratorIntegration.php b/packages/entity-migrator/DependencyInjection/EntityMigratorIntegration.php index 484a02286..14ca9b3b9 100644 --- a/packages/entity-migrator/DependencyInjection/EntityMigratorIntegration.php +++ b/packages/entity-migrator/DependencyInjection/EntityMigratorIntegration.php @@ -6,19 +6,17 @@ use Draw\Component\DependencyInjection\Integration\IntegrationInterface; use Draw\Component\DependencyInjection\Integration\IntegrationTrait; use Draw\Component\DependencyInjection\Integration\PrependIntegrationInterface; -use Draw\Component\EntityMigrator\Command\MigrateCommand; -use Draw\Component\EntityMigrator\Command\QueueBatchCommand; use Draw\Component\EntityMigrator\DependencyInjection\Compiler\EntityMigratorCompilerPass; -use Draw\Component\EntityMigrator\Entity\BaseEntityMigration; use Draw\Component\EntityMigrator\Entity\EntityMigrationInterface; use Draw\Component\EntityMigrator\Entity\Migration; use Draw\Component\EntityMigrator\Message\MigrateEntityCommand; use Draw\Component\EntityMigrator\MigrationInterface; use Draw\Component\EntityMigrator\Migrator; +use Draw\Component\EntityMigrator\Workflow\EntityMigrationWorkflow; +use Draw\Component\EntityMigrator\Workflow\MigrationWorkflow; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; -use Symfony\Component\DependencyInjection\Reference; class EntityMigratorIntegration implements IntegrationInterface, ContainerBuilderIntegrationInterface, PrependIntegrationInterface { @@ -47,16 +45,6 @@ public function load(array $config, PhpFileLoader $loader, ContainerBuilder $con \dirname((new \ReflectionClass(Migrator::class))->getFileName()), ); - $container - ->getDefinition(MigrateCommand::class) - ->setArgument('$servicesResetter', new Reference('services_resetter')) - ; - - $container - ->getDefinition(QueueBatchCommand::class) - ->setArgument('$servicesResetter', new Reference('services_resetter')) - ; - $this->renameDefinitions( $container, $namespace, @@ -130,85 +118,117 @@ public function prepend(ContainerBuilder $container, array $config): void 'framework', [ 'workflows' => [ - 'entity_migration' => [ + MigrationWorkflow::NAME => [ 'type' => 'state_machine', 'marking_store' => [ 'type' => 'method', 'property' => 'state', ], 'supports' => [ - EntityMigrationInterface::class, + Migration::class, ], - 'initial_marking' => BaseEntityMigration::STATE_NEW, - 'places' => [ - BaseEntityMigration::STATE_NEW, - BaseEntityMigration::STATE_QUEUED, - BaseEntityMigration::STATE_PROCESSING, - BaseEntityMigration::STATE_FAILED, - BaseEntityMigration::STATE_COMPLETED, - BaseEntityMigration::STATE_PAUSED, - BaseEntityMigration::STATE_SKIPPED, + 'initial_marking' => MigrationWorkflow::PLACE_NEW, + 'places' => MigrationWorkflow::places(), + 'transitions' => [ + MigrationWorkflow::TRANSITION_PROCESS => [ + 'from' => [ + MigrationWorkflow::PLACE_NEW, + ], + 'to' => MigrationWorkflow::PLACE_PROCESSING, + ], + MigrationWorkflow::TRANSITION_PAUSE => [ + 'from' => [ + MigrationWorkflow::PLACE_NEW, + MigrationWorkflow::PLACE_PROCESSING, + ], + 'to' => MigrationWorkflow::PLACE_PAUSED, + ], + MigrationWorkflow::TRANSITION_COMPLETE => [ + 'from' => [ + MigrationWorkflow::PLACE_PROCESSING, + MigrationWorkflow::PLACE_ERROR, + ], + 'to' => MigrationWorkflow::PLACE_COMPLETED, + ], + MigrationWorkflow::TRANSITION_ERROR => [ + 'from' => [ + MigrationWorkflow::PLACE_PROCESSING, + ], + 'to' => MigrationWorkflow::PLACE_ERROR, + ], + ], + ], + EntityMigrationWorkflow::NAME => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'method', + 'property' => 'state', + ], + 'supports' => [ + EntityMigrationInterface::class, ], + 'initial_marking' => EntityMigrationWorkflow::PLACE_NEW, + 'places' => EntityMigrationWorkflow::places(), 'transitions' => [ - 'queue' => [ + EntityMigrationWorkflow::TRANSITION_QUEUE => [ 'from' => [ - BaseEntityMigration::STATE_NEW, + EntityMigrationWorkflow::PLACE_NEW, ], - 'to' => BaseEntityMigration::STATE_QUEUED, + 'to' => EntityMigrationWorkflow::PLACE_QUEUED, ], - 'pause' => [ + EntityMigrationWorkflow::TRANSITION_PAUSE => [ 'from' => [ - BaseEntityMigration::STATE_NEW, - BaseEntityMigration::STATE_QUEUED, + EntityMigrationWorkflow::PLACE_NEW, + EntityMigrationWorkflow::PLACE_QUEUED, ], - 'to' => BaseEntityMigration::STATE_PAUSED, + 'to' => EntityMigrationWorkflow::PLACE_PAUSED, ], - 'skip' => [ + EntityMigrationWorkflow::TRANSITION_SKIP => [ 'from' => [ - BaseEntityMigration::STATE_NEW, - BaseEntityMigration::STATE_QUEUED, + EntityMigrationWorkflow::PLACE_NEW, + EntityMigrationWorkflow::PLACE_QUEUED, ], - 'to' => BaseEntityMigration::STATE_SKIPPED, + 'to' => EntityMigrationWorkflow::PLACE_SKIPPED, ], - 'process' => [ + EntityMigrationWorkflow::TRANSITION_PROCESS => [ 'from' => [ - BaseEntityMigration::STATE_NEW, - BaseEntityMigration::STATE_QUEUED, + EntityMigrationWorkflow::PLACE_NEW, + EntityMigrationWorkflow::PLACE_QUEUED, ], - 'to' => BaseEntityMigration::STATE_PROCESSING, + 'to' => EntityMigrationWorkflow::PLACE_PROCESSING, ], - 'fail' => [ + EntityMigrationWorkflow::TRANSITION_FAIL => [ 'from' => [ - BaseEntityMigration::STATE_PROCESSING, + EntityMigrationWorkflow::PLACE_PROCESSING, ], - 'to' => 'failed', + 'to' => EntityMigrationWorkflow::PLACE_FAILED, ], - 'complete' => [ + EntityMigrationWorkflow::TRANSITION_COMPLETE => [ 'from' => [ - BaseEntityMigration::STATE_PROCESSING, + EntityMigrationWorkflow::PLACE_PROCESSING, ], - 'to' => BaseEntityMigration::STATE_COMPLETED, + 'to' => EntityMigrationWorkflow::PLACE_COMPLETED, ], - 'retry' => [ + EntityMigrationWorkflow::TRANSITION_RETRY => [ 'from' => [ - 'failed', + EntityMigrationWorkflow::PLACE_FAILED, ], - 'to' => BaseEntityMigration::STATE_QUEUED, + 'to' => EntityMigrationWorkflow::PLACE_QUEUED, ], - 'reprocess' => [ + EntityMigrationWorkflow::TRANSITION_REPROCESS => [ 'from' => [ - BaseEntityMigration::STATE_COMPLETED, - BaseEntityMigration::STATE_SKIPPED, + EntityMigrationWorkflow::PLACE_COMPLETED, + EntityMigrationWorkflow::PLACE_SKIPPED, ], - 'to' => BaseEntityMigration::STATE_QUEUED, + 'to' => EntityMigrationWorkflow::PLACE_QUEUED, ], - 're_queue' => [ + EntityMigrationWorkflow::TRANSITION_REQUEUE => [ 'from' => [ - BaseEntityMigration::STATE_PAUSED, - BaseEntityMigration::STATE_PROCESSING, - BaseEntityMigration::STATE_QUEUED, + EntityMigrationWorkflow::PLACE_PAUSED, + EntityMigrationWorkflow::PLACE_PROCESSING, + EntityMigrationWorkflow::PLACE_QUEUED, ], - 'to' => BaseEntityMigration::STATE_QUEUED, + 'to' => EntityMigrationWorkflow::PLACE_QUEUED, ], ], ], diff --git a/packages/entity-migrator/Entity/BaseEntityMigration.php b/packages/entity-migrator/Entity/BaseEntityMigration.php index a5ba274a8..97892c7a0 100644 --- a/packages/entity-migrator/Entity/BaseEntityMigration.php +++ b/packages/entity-migrator/Entity/BaseEntityMigration.php @@ -4,35 +4,12 @@ use Doctrine\ORM\Mapping as ORM; use Draw\Component\EntityMigrator\MigrationTargetEntityInterface; +use Draw\Component\EntityMigrator\Workflow\EntityMigrationWorkflow; use Draw\Component\Log\Monolog\ErrorToArray; use Symfony\Component\Security\Core\User\UserInterface; abstract class BaseEntityMigration implements EntityMigrationInterface, \Stringable { - public const STATE_NEW = 'new'; - - public const STATE_QUEUED = 'queued'; - - public const STATE_PROCESSING = 'processing'; - - public const STATE_FAILED = 'failed'; - - public const STATE_COMPLETED = 'completed'; - - public const STATE_PAUSED = 'paused'; - - public const STATE_SKIPPED = 'skipped'; - - public const STATES = [ - self::STATE_NEW, - self::STATE_QUEUED, - self::STATE_PROCESSING, - self::STATE_FAILED, - self::STATE_COMPLETED, - self::STATE_PAUSED, - self::STATE_SKIPPED, - ]; - #[ ORM\Id, ORM\GeneratedValue, @@ -49,9 +26,9 @@ abstract class BaseEntityMigration implements EntityMigrationInterface, \Stringa protected Migration $migration; #[ - ORM\Column(type: 'string', nullable: false, options: ['default' => self::STATE_NEW]) + ORM\Column(type: 'string', nullable: false, options: ['default' => EntityMigrationWorkflow::PLACE_NEW]) ] - protected string $state = self::STATE_NEW; + protected string $state = EntityMigrationWorkflow::PLACE_NEW; #[ ORM\Column(type: 'json', nullable: true) diff --git a/packages/entity-migrator/Entity/Migration.php b/packages/entity-migrator/Entity/Migration.php index 95b0e4c55..89386e26d 100644 --- a/packages/entity-migrator/Entity/Migration.php +++ b/packages/entity-migrator/Entity/Migration.php @@ -3,6 +3,7 @@ namespace Draw\Component\EntityMigrator\Entity; use Doctrine\ORM\Mapping as ORM; +use Draw\Component\EntityMigrator\Workflow\MigrationWorkflow; #[ ORM\Entity, @@ -66,7 +67,7 @@ public function setState(string $state): static public function isPaused(): bool { - return 'paused' === $this->state; + return MigrationWorkflow::PLACE_PAUSED === $this->state; } public function __toString(): string diff --git a/packages/entity-migrator/EventListener/WorkflowListener.php b/packages/entity-migrator/EventListener/EntityWorkflowListener.php similarity index 60% rename from packages/entity-migrator/EventListener/WorkflowListener.php rename to packages/entity-migrator/EventListener/EntityWorkflowListener.php index aebd22542..9730a1cc2 100644 --- a/packages/entity-migrator/EventListener/WorkflowListener.php +++ b/packages/entity-migrator/EventListener/EntityWorkflowListener.php @@ -3,28 +3,33 @@ namespace Draw\Component\EntityMigrator\EventListener; use Doctrine\Persistence\ManagerRegistry; -use Draw\Component\EntityMigrator\Entity\BaseEntityMigration; use Draw\Component\EntityMigrator\Entity\EntityMigrationInterface; use Draw\Component\EntityMigrator\Message\MigrateEntityCommand; use Draw\Component\EntityMigrator\MigrationInterface; use Draw\Component\EntityMigrator\Migrator; +use Draw\Component\EntityMigrator\Workflow\EntityMigrationWorkflow; +use Draw\Component\EntityMigrator\Workflow\MigrationWorkflow; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Workflow\Attribute\AsCompletedListener; use Symfony\Component\Workflow\Attribute\AsEnteredListener; use Symfony\Component\Workflow\Attribute\AsGuardListener; use Symfony\Component\Workflow\Event\Event; use Symfony\Component\Workflow\Event\GuardEvent; +use Symfony\Component\Workflow\WorkflowInterface; -class WorkflowListener +class EntityWorkflowListener { public function __construct( private ManagerRegistry $managerRegistry, private Migrator $migrator, private MessageBusInterface $messageBus, + #[Autowire(service: MigrationWorkflow::STATE_MACHINE_NAME)] + private WorkflowInterface $migrationWorkflow, ) { } - #[AsGuardListener('entity_migration', 'pause')] + #[AsGuardListener(EntityMigrationWorkflow::NAME, EntityMigrationWorkflow::TRANSITION_PAUSE)] public function canBePaused(GuardEvent $event): void { if (!$this->getSubject($event)->getMigration()->isPaused()) { @@ -32,7 +37,7 @@ public function canBePaused(GuardEvent $event): void } } - #[AsGuardListener('entity_migration', 'skip')] + #[AsGuardListener(EntityMigrationWorkflow::NAME, EntityMigrationWorkflow::TRANSITION_SKIP)] public function canBeSkip(GuardEvent $event): void { $subject = $this->getSubject($event); @@ -43,13 +48,7 @@ public function canBeSkip(GuardEvent $event): void } } - #[AsGuardListener('entity_migration', 'process')] - public function canBeProcess(GuardEvent $event): void - { - // lock the process using locker - } - - #[AsEnteredListener('entity_migration', BaseEntityMigration::STATE_PROCESSING)] + #[AsEnteredListener(EntityMigrationWorkflow::NAME, EntityMigrationWorkflow::PLACE_PROCESSING)] public function process(Event $event): void { $subject = $this->getSubject($event); @@ -60,7 +59,7 @@ public function process(Event $event): void ; } - #[AsEnteredListener('entity_migration', 'queued')] + #[AsEnteredListener(EntityMigrationWorkflow::NAME, EntityMigrationWorkflow::PLACE_QUEUED)] public function queued(Event $event): void { $this->messageBus->dispatch( @@ -68,7 +67,7 @@ public function queued(Event $event): void ); } - #[AsCompletedListener('entity_migration')] + #[AsCompletedListener(EntityMigrationWorkflow::NAME)] public function flush(Event $event): void { $this->managerRegistry @@ -78,7 +77,22 @@ public function flush(Event $event): void ; } - private function getSubject(GuardEvent|Event $event): EntityMigrationInterface + #[AsCompletedListener(EntityMigrationWorkflow::NAME, priority: -255)] + public function updateState(Event $event): void + { + $entityMigration = $this->getSubject($event); + $migration = $entityMigration->getMigration(); + + foreach (MigrationWorkflow::finalTransitions() as $transition) { + if ($this->migrationWorkflow->can($migration, $transition)) { + $this->migrationWorkflow->apply($migration, $transition); + + return; + } + } + } + + private function getSubject(Event $event): EntityMigrationInterface { $subject = $event->getSubject(); @@ -87,7 +101,7 @@ private function getSubject(GuardEvent|Event $event): EntityMigrationInterface return $subject; } - private function getMigration(GuardEvent|Event $event): MigrationInterface + private function getMigration(Event $event): MigrationInterface { return $this->migrator->getMigration($this->getSubject($event)->getMigration()->getName()); } diff --git a/packages/entity-migrator/EventListener/MigrationWorkflowListener.php b/packages/entity-migrator/EventListener/MigrationWorkflowListener.php new file mode 100644 index 000000000..495fcbc45 --- /dev/null +++ b/packages/entity-migrator/EventListener/MigrationWorkflowListener.php @@ -0,0 +1,207 @@ +getSubject(); + + \assert($subject instanceof Migration); + + $entityMigrationClass = $this->getEntityMigrationClass($subject); + + $entityManager = $this->managerRegistry->getManagerForClass($entityMigrationClass); + + \assert($entityManager instanceof EntityManagerInterface); + + if ($this->gotOneNotInState($entityManager, $entityMigrationClass, $subject, EntityMigrationWorkflow::finalPlaces())) { + $guardEvent->setBlocked(true, 'Some entities still need migration.'); + + return; + } + + if (!$this->gotOneNotInState($entityManager, $entityMigrationClass, $subject, [EntityMigrationWorkflow::PLACE_COMPLETED, EntityMigrationWorkflow::PLACE_SKIPPED])) { + $guardEvent->setBlocked(true, 'None in error.'); + } + } + + #[AsGuardListener(MigrationWorkflow::NAME, MigrationWorkflow::TRANSITION_COMPLETE)] + public function canComplete(GuardEvent $guardEvent): void + { + $subject = $guardEvent->getSubject(); + + \assert($subject instanceof Migration); + + $entityMigrationClass = $this->getEntityMigrationClass($subject); + + $entityManager = $this->managerRegistry->getManagerForClass($entityMigrationClass); + + \assert($entityManager instanceof EntityManagerInterface); + + if ($this->gotOneNotInState($entityManager, $entityMigrationClass, $subject, EntityMigrationWorkflow::finalPlaces())) { + $guardEvent->setBlocked(true, 'Some entities still need migration.'); + + return; + } + + if ($this->gotOneNotInState($entityManager, $entityMigrationClass, $subject, [EntityMigrationWorkflow::PLACE_COMPLETED, EntityMigrationWorkflow::PLACE_SKIPPED])) { + $guardEvent->setBlocked(true, 'One in error.'); + } + } + + private function getEntityMigrationClass(Migration $migration): string + { + $migrationClass = $this->migrator->getMigration($migration->getName())::getTargetEntityClass(); + + return \call_user_func([$migrationClass, 'getEntityMigrationClass']); + } + + private function gotOneNotInState( + EntityManagerInterface $entityManager, + string $entityMigrationClass, + Migration $migration, + array $states, + ): bool { + return null !== $entityManager + ->createQueryBuilder() + ->from($entityMigrationClass, 'entity') + ->andWhere('entity.migration = :migration') + ->andWhere('entity.state NOT IN (:states)') + ->setParameter('migration', $migration) + ->setParameter('states', $states) + ->select('entity.state') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult() + ; + } + + #[AsTransitionListener(MigrationWorkflow::NAME, MigrationWorkflow::TRANSITION_PROCESS)] + public function prepareProcess(TransitionEvent $event): void + { + $subject = $event->getSubject(); + + \assert($subject instanceof Migration); + + $migration = $this->migrator->getMigration($subject->getName()); + + $context = $event->getContext(); + + $context['total'] = $migration->countAllThatNeedMigration(); + + $event->setContext($context); + } + + #[AsCompletedListener(MigrationWorkflow::NAME)] + public function flush(): void + { + $this->managerRegistry + ->getManagerForClass(Migration::class) + ->flush() + ; + } + + #[AsEnteredListener(MigrationWorkflow::NAME, MigrationWorkflow::PLACE_PROCESSING)] + public function process(EnteredEvent $event): void + { + /** @var ?ProgressBar $progressBar */ + $progressBar = $event->getContext()['progressBar'] ?? null; + $subject = $event->getSubject(); + + \assert($subject instanceof Migration); + + $migration = $this->migrator->getMigration($subject->getName()); + + $manager = $this->managerRegistry->getManagerForClass(Migration::class); + + \assert($manager instanceof EntityManagerInterface); + + $entityMigrationMetadata = $manager->getClassMetadata($this->migrator->getMigrationEntityClass($migration)); + + $queryBuilder = $migration->createSelectIdQueryBuilder(); + + $sql = $queryBuilder + ->addSelect( + $queryBuilder->expr()->literal($subject->getId()).' as migration_id', + $queryBuilder->expr()->literal('{}').' as transition_logs', + $queryBuilder->expr()->literal(date('Y-m-d H:i:s')).' as created_at', + ) + ->getQuery() + ->getSQL() + ; + + $manager + ->getConnection() + ->executeStatement( + \sprintf( + 'INSERT IGNORE INTO `%s` (entity_id, migration_id, transition_logs, created_at) %s', + $entityMigrationMetadata->getTableName(), + $sql + ), + array_map( + static fn (Parameter $parameter) => $parameter->getValue(), + $queryBuilder->getParameters()->toArray() + ) + ) + ; + + $queryBuilder = $manager + ->createQueryBuilder() + ->from($entityMigrationMetadata->name, 'entity_migration') + ->andWhere('entity_migration.state = :state') + ->setParameter('state', EntityMigrationWorkflow::PLACE_NEW) + ; + + $count = (int) (clone $queryBuilder) + ->select('count(entity_migration.id)') + ->getQuery() + ->getSingleScalarResult() + ; + + $progressBar?->setMaxSteps($count); + + $result = $queryBuilder + ->select('entity_migration.id as id') + ->getQuery() + ->toIterable() + ; + + foreach ($result as $row) { + $migrationEntity = $manager->getReference($entityMigrationMetadata->name, $row['id']); + + $this->messageBus->dispatch( + new MigrateEntityCommand($migrationEntity) + ); + + $progressBar?->advance(); + } + } +} diff --git a/packages/entity-migrator/Message/MigrateEntityCommand.php b/packages/entity-migrator/Message/MigrateEntityCommand.php index fce3b74fb..65d84cd5b 100644 --- a/packages/entity-migrator/Message/MigrateEntityCommand.php +++ b/packages/entity-migrator/Message/MigrateEntityCommand.php @@ -3,13 +3,10 @@ namespace Draw\Component\EntityMigrator\Message; use Draw\Component\EntityMigrator\Entity\EntityMigrationInterface; -use Draw\Component\Messenger\AutoStamp\Message\StampingAwareInterface; use Draw\Component\Messenger\DoctrineEnvelopeEntityReference\Message\DoctrineReferenceAwareInterface; -use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; -use Symfony\Component\Messenger\Stamp\DelayStamp; -class MigrateEntityCommand implements DoctrineReferenceAwareInterface, StampingAwareInterface +class MigrateEntityCommand implements DoctrineReferenceAwareInterface { private ?EntityMigrationInterface $entity; @@ -31,11 +28,4 @@ public function getPropertiesWithDoctrineObject(): array { return ['entity']; } - - public function stamp(Envelope $envelope): Envelope - { - return $envelope->with( - DelayStamp::delayUntil(new \DateTimeImmutable('+1 minute')) - ); - } } diff --git a/packages/entity-migrator/MigrationInterface.php b/packages/entity-migrator/MigrationInterface.php index 1f8d99957..5a473e712 100644 --- a/packages/entity-migrator/MigrationInterface.php +++ b/packages/entity-migrator/MigrationInterface.php @@ -2,6 +2,8 @@ namespace Draw\Component\EntityMigrator; +use Doctrine\ORM\QueryBuilder; + /** * @template T of MigrationTargetEntityInterface */ @@ -9,6 +11,9 @@ interface MigrationInterface { public static function getName(): string; + /** + * @return class-string + */ public static function getTargetEntityClass(): string; /** @@ -37,4 +42,6 @@ public function countAllThatNeedMigration(): ?int; * Return a boolean to indicate that no more entities need migration. */ public function migrationIsCompleted(): bool; + + public function createSelectIdQueryBuilder(): QueryBuilder; } diff --git a/packages/entity-migrator/Migrator.php b/packages/entity-migrator/Migrator.php index d1cd6d13f..1368f1b86 100644 --- a/packages/entity-migrator/Migrator.php +++ b/packages/entity-migrator/Migrator.php @@ -6,8 +6,10 @@ use Draw\Component\EntityMigrator\Entity\EntityMigrationInterface; use Draw\Component\EntityMigrator\Entity\Migration; use Draw\Component\EntityMigrator\Repository\EntityMigrationRepository; +use Draw\Component\EntityMigrator\Workflow\EntityMigrationWorkflow; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Workflow\WorkflowInterface; @@ -15,7 +17,8 @@ class Migrator { public function __construct( private ContainerInterface $migrations, - private WorkflowInterface $entityMigrationStateMachine, + #[Autowire(service: EntityMigrationWorkflow::STATE_MACHINE_NAME)] + private WorkflowInterface $workflow, private EntityMigrationRepository $entityMigrationRepository, private ManagerRegistry $managerRegistry, private LockFactory $entityMigratorLockFactory, @@ -26,11 +29,14 @@ public function __construct( public function queue(EntityMigrationInterface $entity): void { - if ($this->entityMigrationStateMachine->can($entity, 'queue')) { - $this->entityMigrationStateMachine->apply($entity, 'queue'); + if ($this->workflow->can($entity, 'queue')) { + $this->workflow->apply($entity, 'queue'); } } + /** + * This is called directly from a controller to do a just in time migration. + */ public function migrateEntity(MigrationTargetEntityInterface $entity, string $migrationName): void { if (class_exists($migrationName)) { @@ -55,10 +61,7 @@ public function migrateEntity(MigrationTargetEntityInterface $entity, string $mi $this->migrate($entityMigration, true); } - /** - * @return bool true if a transition was applied, false if the entity is already being migrated - */ - public function migrate(EntityMigrationInterface $entityMigration, bool $wait = false): bool + public function migrate(EntityMigrationInterface $entityMigration, bool $wait = false): void { $lock = $this->entityMigratorLockFactory->createLock( 'entity-migrator-'.$entityMigration->getMigration()->getName().'-'.$entityMigration->getId() @@ -76,7 +79,7 @@ public function migrate(EntityMigrationInterface $entityMigration, bool $wait = ); if (!$acquired && !$wait) { - return false; + return; } if (!$acquired) { @@ -98,34 +101,27 @@ public function migrate(EntityMigrationInterface $entityMigration, bool $wait = ; } - $transitionApplied = false; - - foreach (['paused', 'skip', 'process'] as $transition) { - if (!$this->entityMigrationStateMachine->can($entityMigration, $transition)) { + foreach ([EntityMigrationWorkflow::TRANSITION_PAUSE, EntityMigrationWorkflow::TRANSITION_SKIP, EntityMigrationWorkflow::TRANSITION_PROCESS] as $transition) { + if (!$this->workflow->can($entityMigration, $transition)) { continue; } - - $transitionApplied = true; try { - $this->entityMigrationStateMachine->apply($entityMigration, $transition); + $this->workflow->apply($entityMigration, $transition); break; } catch (\Throwable $error) { - $this->entityMigrationStateMachine->apply($entityMigration, 'fail', ['error' => $error]); + $this->workflow->apply($entityMigration, EntityMigrationWorkflow::TRANSITION_FAIL, ['error' => $error]); - return true; + return; } } - if ($this->entityMigrationStateMachine->can($entityMigration, 'complete')) { - $transitionApplied = true; - $this->entityMigrationStateMachine->apply($entityMigration, 'complete'); + if ($this->workflow->can($entityMigration, EntityMigrationWorkflow::TRANSITION_COMPLETE)) { + $this->workflow->apply($entityMigration, EntityMigrationWorkflow::TRANSITION_COMPLETE); } - - return $transitionApplied; } - public function getMigration(string $name): MigrationInterface|BatchPrepareMigrationInterface + public function getMigration(string $name): MigrationInterface { return $this->migrations->get($name); } @@ -139,4 +135,14 @@ public function getMigrations(): iterable yield $name => $this->getMigration($name); } } + + /** + * @return class-string + */ + public function getMigrationEntityClass(MigrationInterface $migration): string + { + $class = $migration::getTargetEntityClass(); + + return \call_user_func([$class, 'getEntityMigrationClass']); + } } diff --git a/packages/entity-migrator/Tests/Message/MigrateEntityCommandTest.php b/packages/entity-migrator/Tests/Message/MigrateEntityCommandTest.php index b5131e55f..d9e51d001 100644 --- a/packages/entity-migrator/Tests/Message/MigrateEntityCommandTest.php +++ b/packages/entity-migrator/Tests/Message/MigrateEntityCommandTest.php @@ -7,9 +7,7 @@ use Draw\Component\EntityMigrator\Message\MigrateEntityCommand; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; -use Symfony\Component\Messenger\Stamp\DelayStamp; /** * @internal @@ -55,15 +53,4 @@ public function testGetPropertiesWithDoctrineObject(): void $this->object->getPropertiesWithDoctrineObject() ); } - - public function testStamp(): void - { - $envelope = $this->object->stamp(new Envelope(new \stdClass())); - - static::assertEqualsWithDelta( - (strtotime('+1 minute') - time()) * 1000, - $envelope->last(DelayStamp::class)->getDelay(), - 1000 - ); - } } diff --git a/packages/entity-migrator/Workflow/EntityMigrationWorkflow.php b/packages/entity-migrator/Workflow/EntityMigrationWorkflow.php new file mode 100644 index 000000000..2ec760cf4 --- /dev/null +++ b/packages/entity-migrator/Workflow/EntityMigrationWorkflow.php @@ -0,0 +1,71 @@ + + */ + public static function places(): array + { + $reflection = new \ReflectionClass(__CLASS__); + + return array_values( + array_filter( + $reflection->getConstants(), + static fn ($constant) => str_starts_with($constant, 'PLACE_'), + \ARRAY_FILTER_USE_KEY + ) + ); + } + + public static function finalPlaces(): array + { + return [ + self::PLACE_COMPLETED, + self::PLACE_FAILED, + self::PLACE_SKIPPED, + ]; + } +} diff --git a/packages/entity-migrator/Workflow/MigrationWorkflow.php b/packages/entity-migrator/Workflow/MigrationWorkflow.php new file mode 100644 index 000000000..1bf04656e --- /dev/null +++ b/packages/entity-migrator/Workflow/MigrationWorkflow.php @@ -0,0 +1,61 @@ + + */ + public static function places(): array + { + $reflection = new \ReflectionClass(__CLASS__); + + return array_values( + array_filter( + $reflection->getConstants(), + static fn ($constant) => str_starts_with($constant, 'PLACE_'), + \ARRAY_FILTER_USE_KEY + ) + ); + } + + /** + * Return the list of transition that are final. + * + * @return array + */ + public static function finalTransitions(): array + { + return [ + self::TRANSITION_COMPLETE, + self::TRANSITION_ERROR, + ]; + } +} diff --git a/packages/sonata-extra-bundle/Controller/WorkflowTransitionAction.php b/packages/sonata-extra-bundle/ActionableAdmin/Action/WorkflowTransitionAction.php similarity index 92% rename from packages/sonata-extra-bundle/Controller/WorkflowTransitionAction.php rename to packages/sonata-extra-bundle/ActionableAdmin/Action/WorkflowTransitionAction.php index 8f3073ad7..26240e695 100644 --- a/packages/sonata-extra-bundle/Controller/WorkflowTransitionAction.php +++ b/packages/sonata-extra-bundle/ActionableAdmin/Action/WorkflowTransitionAction.php @@ -1,6 +1,6 @@ toString($object))); } - $marking = $workflow->apply($object, $transition); + $workflow->apply($object, $transition); $admin->update($object); }, diff --git a/packages/sonata-extra-bundle/ActionableAdmin/AdminAction/WorkflowTransitionAdminAction.php b/packages/sonata-extra-bundle/ActionableAdmin/AdminAction/WorkflowTransitionAdminAction.php new file mode 100644 index 000000000..604ceb5f7 --- /dev/null +++ b/packages/sonata-extra-bundle/ActionableAdmin/AdminAction/WorkflowTransitionAdminAction.php @@ -0,0 +1,20 @@ +setController(WorkflowTransitionAction::class) + ->clearForActions() + ->setForEntityListAction(false) + ; + } +} diff --git a/packages/sonata-extra-bundle/Extension/WorkflowExtension.php b/packages/sonata-extra-bundle/Extension/WorkflowExtension.php index 7508a0655..5acdb2fc8 100644 --- a/packages/sonata-extra-bundle/Extension/WorkflowExtension.php +++ b/packages/sonata-extra-bundle/Extension/WorkflowExtension.php @@ -2,9 +2,8 @@ namespace Draw\Bundle\SonataExtraBundle\Extension; -use Draw\Bundle\SonataExtraBundle\ActionableAdmin\AdminAction; +use Draw\Bundle\SonataExtraBundle\ActionableAdmin\AdminAction\WorkflowTransitionAdminAction; use Draw\Bundle\SonataExtraBundle\ActionableAdmin\Extension\ActionableAdminExtensionInterface; -use Draw\Bundle\SonataExtraBundle\Controller\WorkflowTransitionAction; use Sonata\AdminBundle\Admin\AdminInterface; use Sonata\AdminBundle\Route\RouteCollectionInterface; @@ -17,15 +16,6 @@ public function configureRoutes(AdminInterface $admin, RouteCollectionInterface public function getActions(array $actions): array { - $actions['workflow_apply_transition'] = (new AdminAction( - 'workflow_apply_transition', - true, - )) - ->setController(WorkflowTransitionAction::class) - ->clearForActions() - ->setForEntityListAction(false) - ; - - return $actions; + return $actions + ['workflow_apply_transition' => new WorkflowTransitionAdminAction()]; } } diff --git a/packages/sonata-extra-bundle/Tests/DependencyInjection/ConfigurationTest.php b/packages/sonata-extra-bundle/Tests/DependencyInjection/ConfigurationTest.php index 70992e87e..5e3ad03b8 100644 --- a/packages/sonata-extra-bundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/packages/sonata-extra-bundle/Tests/DependencyInjection/ConfigurationTest.php @@ -65,6 +65,9 @@ public function getDefaultConfiguration(): array 'enabled' => false, 'delay' => 3600, ], + 'workflow' => [ + 'enabled' => false, + ], ]; } diff --git a/packages/sonata-integration-bundle/DependencyInjection/Configuration.php b/packages/sonata-integration-bundle/DependencyInjection/Configuration.php index f7fce9070..e7bfd64fd 100644 --- a/packages/sonata-integration-bundle/DependencyInjection/Configuration.php +++ b/packages/sonata-integration-bundle/DependencyInjection/Configuration.php @@ -129,6 +129,7 @@ private function createEntityMigratorNode(): ArrayNodeDefinition ->append( (new SonataAdminNodeConfiguration(Migration::class, 'Entity Migrator', 'admin')) ->addDefaultsIfNotSet() + ->translationDomainDefaultValue('DrawEntityMigratorAdmin') ->labelDefaultValue('Migration') ->iconDefaultValue('fa fa-cogs') ) diff --git a/packages/sonata-integration-bundle/DependencyInjection/DrawSonataIntegrationExtension.php b/packages/sonata-integration-bundle/DependencyInjection/DrawSonataIntegrationExtension.php index e596f33f3..1cb82f92c 100644 --- a/packages/sonata-integration-bundle/DependencyInjection/DrawSonataIntegrationExtension.php +++ b/packages/sonata-integration-bundle/DependencyInjection/DrawSonataIntegrationExtension.php @@ -217,7 +217,7 @@ private function configureEntityMigrator(array $config, Loader\FileLoader $loade $container ->setDefinition( - 'draw.sonata_integration.extension.workflow.entity_migrator', + 'draw.sonata_integration.extension.workflow.entity_migration_admin', new Definition(WorkflowExtension::class), ) ->setAutowired(true) @@ -230,6 +230,7 @@ private function configureEntityMigrator(array $config, Loader\FileLoader $loade ] ) ->addTag('sonata.admin.extension', ['admin_instanceof' => BaseEntityMigrationAdmin::class]) + ->addTag('sonata.admin.extension', ['admin_instanceof' => MigrationAdmin::class]) ; } diff --git a/packages/sonata-integration-bundle/EntityMigrator/Admin/BaseEntityMigrationAdmin.php b/packages/sonata-integration-bundle/EntityMigrator/Admin/BaseEntityMigrationAdmin.php index 2db10e8ee..e046b005d 100644 --- a/packages/sonata-integration-bundle/EntityMigrator/Admin/BaseEntityMigrationAdmin.php +++ b/packages/sonata-integration-bundle/EntityMigrator/Admin/BaseEntityMigrationAdmin.php @@ -2,7 +2,7 @@ namespace Draw\Bundle\SonataIntegrationBundle\EntityMigrator\Admin; -use Draw\Component\EntityMigrator\Entity\BaseEntityMigration; +use Draw\Component\EntityMigrator\Workflow\EntityMigrationWorkflow; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Datagrid\ListMapper; @@ -46,8 +46,8 @@ protected function configureDatagridFilters(DatagridMapper $filter): void 'field_options' => [ 'multiple' => true, 'choices' => array_combine( - BaseEntityMigration::STATES, - BaseEntityMigration::STATES + $places = EntityMigrationWorkflow::places(), + $places ), ], 'show_filter' => true, diff --git a/packages/sonata-integration-bundle/Resources/translations/DrawEntityMigratorAdmin.en.yaml b/packages/sonata-integration-bundle/Resources/translations/DrawEntityMigratorAdmin.en.yaml index 22f1357b9..ac991c232 100644 --- a/packages/sonata-integration-bundle/Resources/translations/DrawEntityMigratorAdmin.en.yaml +++ b/packages/sonata-integration-bundle/Resources/translations/DrawEntityMigratorAdmin.en.yaml @@ -1 +1 @@ -workflow.dropdown_transitions_label: 'Transitions' \ No newline at end of file +workflow.dropdown_transitions_label: 'Transitions' diff --git a/packages/sonata-integration-bundle/Tests/DependencyInjection/ConfigurationTest.php b/packages/sonata-integration-bundle/Tests/DependencyInjection/ConfigurationTest.php index e8225081b..5fbfd4acb 100644 --- a/packages/sonata-integration-bundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/packages/sonata-integration-bundle/Tests/DependencyInjection/ConfigurationTest.php @@ -95,7 +95,7 @@ public function getDefaultConfiguration(): array 'label' => 'Migration', 'pager_type' => 'default', 'show_in_dashboard' => true, - 'translation_domain' => 'SonataAdminBundle', + 'translation_domain' => 'DrawEntityMigratorAdmin', ], ], 'messenger' => [ diff --git a/tests/EntityMigrator/Command/MigrateCommandTest.php b/tests/EntityMigrator/Command/MigrateCommandTest.php new file mode 100644 index 000000000..18e825a1e --- /dev/null +++ b/tests/EntityMigrator/Command/MigrateCommandTest.php @@ -0,0 +1,53 @@ +execute([ + 'migration-name' => 'user-set-comment-null', + ]) + ->test( + CommandDataTester::create() + ->setExpectedDisplay(['[OK] Migration started']) + ->setExpectedErrorOutput(null) + ) + ; + } +} diff --git a/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml b/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml index 42149d9a3..3f94383e1 100644 --- a/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml +++ b/tests/fixtures/AppKernelTest/testEventDispatcherConfiguration/event_dispatcher.xml @@ -255,23 +255,36 @@ - - + + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + +