Skip to content

Commit

Permalink
[EntityMigrator] new component
Browse files Browse the repository at this point in the history
  • Loading branch information
mpoiriert committed Oct 25, 2023
1 parent 29fd7cb commit 41ce41b
Show file tree
Hide file tree
Showing 29 changed files with 1,131 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/after_spliting_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
- console
- core
- doctrine-extra
- entity-migrator
- fixer
- framework-extra-bundle
- log
Expand Down
59 changes: 59 additions & 0 deletions app/migrations/Version20231023001449.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20231023001449 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE draw_entity_migrator__migration (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
state VARCHAR(255) NOT NULL,
UNIQUE INDEX name (name),
PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE user_migration (
id BIGINT AUTO_INCREMENT NOT NULL,
entity_id CHAR(36) NOT NULL COMMENT \'(DC2Type:guid)\',
migration_id INT NOT NULL,
state VARCHAR(255) DEFAULT \'new\' NOT NULL,
transition_logs JSON DEFAULT NULL,
created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\',
INDEX IDX_C3FC382681257D5D (entity_id),
INDEX IDX_C3FC382679D9816F (migration_id),
PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE
user_migration
ADD
CONSTRAINT FK_C3FC382681257D5D FOREIGN KEY (entity_id) REFERENCES draw_acme__user (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE
user_migration
ADD
CONSTRAINT FK_C3FC382679D9816F FOREIGN KEY (migration_id) REFERENCES draw_entity_migrator__migration (id) ON DELETE CASCADE');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE user_migration DROP FOREIGN KEY FK_C3FC382681257D5D');
$this->addSql('ALTER TABLE user_migration DROP FOREIGN KEY FK_C3FC382679D9816F');
$this->addSql('DROP TABLE draw_entity_migrator__migration');
$this->addSql('DROP TABLE user_migration');
}
}
8 changes: 7 additions & 1 deletion app/src/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Draw\Bundle\UserBundle\Security\TwoFactorAuthentication\Entity\ByTimeBaseOneTimePasswordTrait;
use Draw\Bundle\UserBundle\Security\TwoFactorAuthentication\Entity\ConfigurationTrait;
use Draw\Bundle\UserBundle\Security\TwoFactorAuthentication\Entity\TwoFactorAuthenticationUserInterface;
use Draw\Component\EntityMigrator\MigrationTargetEntityInterface;
use Draw\Component\Messenger\DoctrineMessageBusHook\Entity\MessageHolderInterface;
use Draw\Component\Messenger\DoctrineMessageBusHook\Entity\MessageHolderTrait;
use Draw\DoctrineExtra\Common\Collections\CollectionUtil;
Expand All @@ -33,7 +34,7 @@
#[ORM\Table(name: 'draw_acme__user')]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['email'])]
class User implements MessageHolderInterface, SecurityUserInterface, TwoFactorAuthenticationUserInterface, PasswordChangeUserInterface, LockableUserInterface, TwoFactorInterface, ByEmailInterface, ByTimeBaseOneTimePasswordInterface
class User implements MessageHolderInterface, SecurityUserInterface, TwoFactorAuthenticationUserInterface, PasswordChangeUserInterface, LockableUserInterface, TwoFactorInterface, ByEmailInterface, ByTimeBaseOneTimePasswordInterface, MigrationTargetEntityInterface
{
use ByEmailTrait;
use ByTimeBaseOneTimePasswordTrait;
Expand Down Expand Up @@ -398,4 +399,9 @@ public function setRequiredReadOnly(string $requiredReadOnly): static

return $this;
}

public static function getEntityMigrationClass(): string
{
return UserMigration::class;
}
}
20 changes: 20 additions & 0 deletions app/src/Entity/UserMigration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Draw\Component\EntityMigrator\Entity\BaseEntityMigration;
use Draw\Component\EntityMigrator\MigrationTargetEntityInterface;

#[
ORM\Entity,
ORM\Table(name: 'user_migration'),
]
class UserMigration extends BaseEntityMigration
{
#[
ORM\ManyToOne(targetEntity: User::class),
ORM\JoinColumn(name: 'entity_id', nullable: false, onDelete: 'CASCADE')
]
protected MigrationTargetEntityInterface $entity;
}
79 changes: 79 additions & 0 deletions app/src/EntityMigration/UserSetCommentNullMigration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace App\EntityMigration;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Draw\Component\EntityMigrator\MigrationInterface;
use Draw\Component\EntityMigrator\MigrationTargetEntityInterface;

/**
* @template-implements MigrationInterface<User>
*/
class UserSetCommentNullMigration implements MigrationInterface
{
public static function getName(): string
{
return 'user-set-comment-null';
}

public function __construct(private ManagerRegistry $managerRegistry)
{

}

public static function getTargetEntityClass(): string
{
return User::class;
}

public function migrate(MigrationTargetEntityInterface $entity): void
{
$entity->setComment('');
}

public function needMigration(MigrationTargetEntityInterface $entity): bool
{
return '' !== $entity->getComment();
}

public function findAllThatNeedMigration(): iterable
{
$manager = $this->managerRegistry->getManagerForClass(User::class);
\assert($manager instanceof EntityManagerInterface);

$query = $manager
->createQuery('SELECT user.id FROM '.User::class.' user WHERE user.comment != :comment');

foreach ($query->toIterable(['comment' => ''], $query::HYDRATE_SCALAR) as $userId) {
yield $manager->getReference(User::class, $userId['id']);
}
}

public function countAllThatNeedMigration(): ?int
{
$manager = $this->managerRegistry->getManagerForClass(User::class);
\assert($manager instanceof EntityManagerInterface);

return (int) $manager
->createQuery('SELECT count(user) FROM '.User::class.' user WHERE user.comment != :comment')
->setParameter('comment', '')
->getSingleScalarResult();
}

public function migrationIsCompleted(): bool
{
$repository = $this->managerRegistry->getRepository(User::class);
\assert($repository instanceof EntityRepository);

return null === $repository
->createQueryBuilder('user')
->select('user.id')
->where('user.comment != ""')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"draw/contracts": "self.version",
"draw/core": "self.version",
"draw/doctrine-extra": "self.version",
"draw/entity-migrator": "self.version",
"draw/fixer": "self.version",
"draw/framework-extra-bundle": "self.version",
"draw/log": "self.version",
Expand Down Expand Up @@ -168,6 +169,7 @@
"Draw\\Component\\AwsToolKit\\": "packages/aws-tool-kit/",
"Draw\\Component\\Console\\": "packages/console/",
"Draw\\Component\\Core\\": "packages/core/",
"Draw\\Component\\EntityMigrator\\": "packages/entity-migrator/",
"Draw\\Component\\Log\\": "packages/log/",
"Draw\\Component\\Mailer\\": "packages/mailer/",
"Draw\\Component\\Messenger\\": "packages/messenger/",
Expand Down
4 changes: 4 additions & 0 deletions config/packages/draw_framework_extra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ draw_framework_extra:

doctrine_extra: ~

entity_migrator: ~

feature: ~

log:
Expand Down Expand Up @@ -90,3 +92,5 @@ draw_framework_extra:
validator: ~

versioning: ~

workflow: ~
7 changes: 7 additions & 0 deletions packages/console/Tests/Output/BufferedConsoleOutputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Draw\Component\Console\Tests\Output;

use Draw\Component\Console\Output\BufferedConsoleOutput;
use Draw\Component\Core\Reflection\ReflectionAccessor;
use Draw\Component\Tester\MockTrait;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -75,6 +76,12 @@ public function testFetch(): void

$message = uniqid('message-');

ReflectionAccessor::setPropertyValue(
$this->object,
'stream',
tmpfile()
);

$this->object->write($message, true);

static::assertSame(
Expand Down
8 changes: 8 additions & 0 deletions packages/entity-migrator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/vendor/
/.idea/
composer.lock

###> phpunit/phpunit ###
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###
119 changes: 119 additions & 0 deletions packages/entity-migrator/Command/MigrateAllCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Draw\Component\EntityMigrator\Command;

use Doctrine\Persistence\ManagerRegistry;
use Draw\Component\EntityMigrator\Entity\Migration;
use Draw\Component\EntityMigrator\Migrator;
use Draw\Component\EntityMigrator\Repository\EntityMigrationRepository;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
name: 'draw:entity-migrator:migrate-all',
description: 'Migrate all entities',
)]
class MigrateAllCommand extends Command
{
public function __construct(
private Migrator $migrator,
private EntityMigrationRepository $entityMigrationRepository,
private ManagerRegistry $managerRegistry
) {
parent::__construct();
}

protected function configure(): void
{
$this
->addArgument('migration-name', null, 'The migration name to migrate')
->addOption('now', null, InputOption::VALUE_NONE, 'Execute the migration now');
}

protected function interact(InputInterface $input, OutputInterface $output): void
{
$io = new SymfonyStyle($input, $output);

if (!$input->getArgument('migration-name')) {
$action = $input->getOption('now') ? 'process' : 'queue';
$io->block(
sprintf(
'Which migration do you want to %s?',
$action
),
null,
'fg=white;bg=blue',
' ',
true
);

$question = new ChoiceQuestion(
'Select which migration',
array_map(
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(
$input,
$output
);

$now = (bool) $input->getOption('now');

$migration = $this->migrator->getMigration($input->getArgument('migration-name'));

$migrationEntity = $this->managerRegistry
->getRepository(Migration::class)
->findOneBy(['name' => $migration::getName()]);

$count = $migration->countAllThatNeedMigration();

if (0 === $count) {
$io->warning('No entity need migration');

return Command::SUCCESS;
}

$progress = $io->createProgressBar($count ?? 0);

$realCount = 0;
foreach ($migration->findAllThatNeedMigration() as $entity) {
$entityMigration = $this->entityMigrationRepository->load($entity, $migrationEntity);

if ($now) {
$this->migrator->migrate($entityMigration);
} else {
$this->migrator->queue($entityMigration);
}

++$realCount;
$progress->advance();
}

$progress->finish();

$io->newLine();

$io->success(sprintf(
'Migration %s %s for %d entities',
$migration::getName(),
$now ? 'processed' : 'queued',
$realCount
));

return Command::SUCCESS;
}
}
Loading

0 comments on commit 41ce41b

Please sign in to comment.