Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispatch events for migration execution #114

Merged
merged 3 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,23 @@ Implement `executeQuery()` to run checks or other code before/after each query.
Interface of this method mimics interface of `Doctrine\DBAL\Connection::executeQuery()`.

#### Alter generated migration SQLs:

You can implement custom `MigrationAnalyzer` and register it as a service.
This allows you to alter generated SQLs (e.g. add `ALGORITHM=INSTANT`) and assign them to proper phase.

#### Hook to migration execution:

If you pass `Psr\EventDispatcher\EventDispatcherInterface` to `MigrationService`, you can hook into migration execution.
Dispatched events are:
- `MigrationExecutionStartedEvent`
- `MigrationExecutionSucceededEvent`
- `MigrationExecutionFailedEvent`

All events have `MigrationPhase` enum and `Migration` instance available.

#### Run all queries within transaction:

You can change your template (or a single migration) to extend; `TransactionalMigration`.
You can change your template (or a single migration) to extend `TransactionalMigration`.
That causes each phases to be executed within migration.
Be aware that many databases (like MySQL) does not support transaction over DDL operations (ALTER and such).

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"php": "^8.1",
"doctrine/dbal": "^3.6.0 || ^4.0.0",
"doctrine/orm": "^3.0.0",
"psr/event-dispatcher": "^1.0",
"symfony/console": "^5.4.0 || ^6.0.0 || ^7.0.0"
},
"require-dev": {
Expand Down
66 changes: 60 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@
<rule ref="SlevomatCodingStandard.Classes.RequireSelfReference"/>
<rule ref="SlevomatCodingStandard.Classes.TraitUseDeclaration" />
<rule ref="SlevomatCodingStandard.Classes.TraitUseSpacing" />
<rule ref="SlevomatCodingStandard.Classes.DisallowConstructorPropertyPromotion" />
<rule ref="SlevomatCodingStandard.Commenting.DocCommentSpacing">
<properties>
<property name="linesCountBeforeFirstContent" value="0"/>
Expand Down
21 changes: 21 additions & 0 deletions src/Event/MigrationExecutionFailedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

namespace ShipMonk\Doctrine\Migration\Event;

use ShipMonk\Doctrine\Migration\Migration;
use ShipMonk\Doctrine\Migration\MigrationPhase;
use Throwable;

class MigrationExecutionFailedEvent
{

public function __construct(
public readonly Migration $migration,
public readonly string $version,
public readonly MigrationPhase $phase,
public readonly Throwable $exception,
)
{
}

}
19 changes: 19 additions & 0 deletions src/Event/MigrationExecutionStartedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace ShipMonk\Doctrine\Migration\Event;

use ShipMonk\Doctrine\Migration\Migration;
use ShipMonk\Doctrine\Migration\MigrationPhase;

class MigrationExecutionStartedEvent
{

public function __construct(
public readonly Migration $migration,
public readonly string $version,
public readonly MigrationPhase $phase,
)
{
}

}
19 changes: 19 additions & 0 deletions src/Event/MigrationExecutionSucceededEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace ShipMonk\Doctrine\Migration\Event;

use ShipMonk\Doctrine\Migration\Migration;
use ShipMonk\Doctrine\Migration\MigrationPhase;

class MigrationExecutionSucceededEvent
{

public function __construct(
public readonly Migration $migration,
public readonly string $version,
public readonly MigrationPhase $phase,
)
{
}

}
39 changes: 26 additions & 13 deletions src/MigrationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use LogicException;
use Psr\EventDispatcher\EventDispatcherInterface;
use ShipMonk\Doctrine\Migration\Event\MigrationExecutionFailedEvent;
use ShipMonk\Doctrine\Migration\Event\MigrationExecutionStartedEvent;
use ShipMonk\Doctrine\Migration\Event\MigrationExecutionSucceededEvent;
use Throwable;
use function file_get_contents;
use function file_put_contents;
Expand All @@ -33,22 +37,26 @@ class MigrationService

private MigrationVersionProvider $versionProvider;

private MigrationAnalyzer $migrationsAnalyzer;
private MigrationAnalyzer $migrationAnalyzer;

private ?EventDispatcherInterface $eventDispatcher;

public function __construct(
EntityManagerInterface $entityManager,
MigrationConfig $config,
?MigrationExecutor $executor = null,
?MigrationVersionProvider $versionProvider = null,
?MigrationAnalyzer $migrationsAnalyzer = null,
?MigrationAnalyzer $migrationAnalyzer = null,
?EventDispatcherInterface $eventDispatcher = null,
)
{
$this->entityManager = $entityManager;
$this->connection = $entityManager->getConnection();
$this->config = $config;
$this->executor = $executor ?? new MigrationDefaultExecutor($this->connection);
$this->versionProvider = $versionProvider ?? new MigrationDefaultVersionProvider();
$this->migrationsAnalyzer = $migrationsAnalyzer ?? new MigrationDefaultAnalyzer();
$this->migrationAnalyzer = $migrationAnalyzer ?? new MigrationDefaultAnalyzer();
$this->eventDispatcher = $eventDispatcher;
}

public function getConfig(): MigrationConfig
Expand All @@ -67,17 +75,22 @@ public function executeMigration(string $version, MigrationPhase $phase): Migrat
{
$migration = $this->getMigration($version);

if ($migration instanceof TransactionalMigration) {
try {
$this->connection->beginTransaction();
$this->eventDispatcher?->dispatch(new MigrationExecutionStartedEvent($migration, $version, $phase));

try {
if ($migration instanceof TransactionalMigration) {
$run = $this->connection->transactional(function () use ($migration, $version, $phase): MigrationRun {
return $this->doExecuteMigration($migration, $version, $phase);
});
} else {
$run = $this->doExecuteMigration($migration, $version, $phase);
$this->connection->commit();
} catch (Throwable $e) {
$this->connection->rollBack();
throw $e;
}
} else {
$run = $this->doExecuteMigration($migration, $version, $phase);

$this->eventDispatcher?->dispatch(new MigrationExecutionSucceededEvent($migration, $version, $phase));

} catch (Throwable $e) {
$this->eventDispatcher?->dispatch(new MigrationExecutionFailedEvent($migration, $version, $phase, $e));
throw $e;
}

return $run;
Expand Down Expand Up @@ -231,7 +244,7 @@ private function excludeTablesFromSchema(Schema $schema): void
*/
public function generateMigrationFile(array $sqls): MigrationFile
{
$statements = $this->migrationsAnalyzer->analyze($sqls);
$statements = $this->migrationAnalyzer->analyze($sqls);
$statementsBefore = $statementsAfter = [];

foreach ($statements as $statement) {
Expand Down
24 changes: 22 additions & 2 deletions tests/MigrationServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use Doctrine\ORM\EntityManagerInterface;
use LogicException;
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;
use ShipMonk\Doctrine\Migration\Event\MigrationExecutionStartedEvent;
use ShipMonk\Doctrine\Migration\Event\MigrationExecutionSucceededEvent;
use function array_map;
use function file_get_contents;
use function glob;
Expand All @@ -22,9 +25,24 @@ class MigrationServiceTest extends TestCase

public function testInitGenerationExecution(): void
{
$invokedCount = self::exactly(4);
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$eventDispatcher->expects($invokedCount)
->method('dispatch')
->willReturnCallback(static function (object $event) use ($invokedCount): object {
match ($invokedCount->numberOfInvocations()) {
1 => self::assertInstanceOf(MigrationExecutionStartedEvent::class, $event, $event::class),
2 => self::assertInstanceOf(MigrationExecutionSucceededEvent::class, $event, $event::class),
3 => self::assertInstanceOf(MigrationExecutionStartedEvent::class, $event, $event::class),
4 => self::assertInstanceOf(MigrationExecutionSucceededEvent::class, $event, $event::class),
default => self::fail('Unexpected event'),
};
return $event;
});

[$entityManager] = $this->createEntityManagerAndLogger();
$connection = $entityManager->getConnection();
$service = $this->createMigrationService($entityManager);
$service = $this->createMigrationService($entityManager, eventDispatcher: $eventDispatcher);

$migrationTableName = $service->getConfig()->getMigrationTableName();

Expand Down Expand Up @@ -134,7 +152,7 @@ public function getNextVersion(): string
};
[$entityManager, $logger] = $this->createEntityManagerAndLogger();

$migrationsService = $this->createMigrationService($entityManager, [], false, $versionProvider);
$migrationsService = $this->createMigrationService($entityManager, versionProvider: $versionProvider);

$migrationsService->initializeMigrationTable();
$logger->clean();
Expand Down Expand Up @@ -296,6 +314,7 @@ private function createMigrationService(
bool $transactional = false,
?MigrationVersionProvider $versionProvider = null,
?MigrationAnalyzer $statementAnalyzer = null,
?EventDispatcherInterface $eventDispatcher = null,
): MigrationService
{
$migrationsDir = $this->getMigrationsTestDir();
Expand Down Expand Up @@ -329,6 +348,7 @@ private function createMigrationService(
null,
$versionProvider,
$statementAnalyzer,
$eventDispatcher,
);
}

Expand Down