164 lines
5.0 KiB
PHP
164 lines
5.0 KiB
PHP
|
<?php
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
namespace Doctrine\Migrations;
|
||
|
|
||
|
use Doctrine\DBAL\Connection;
|
||
|
use Doctrine\Migrations\Exception\MigrationConfigurationConflict;
|
||
|
use Doctrine\Migrations\Metadata\MigrationPlanList;
|
||
|
use Doctrine\Migrations\Query\Query;
|
||
|
use Doctrine\Migrations\Tools\BytesFormatter;
|
||
|
use Doctrine\Migrations\Tools\TransactionHelper;
|
||
|
use Doctrine\Migrations\Version\Executor;
|
||
|
use Psr\Log\LoggerInterface;
|
||
|
use Symfony\Component\Stopwatch\Stopwatch;
|
||
|
use Symfony\Component\Stopwatch\StopwatchEvent;
|
||
|
use Throwable;
|
||
|
|
||
|
use function count;
|
||
|
|
||
|
use const COUNT_RECURSIVE;
|
||
|
|
||
|
/**
|
||
|
* The DbalMigrator class is responsible for generating and executing the SQL for a migration.
|
||
|
*
|
||
|
* @internal
|
||
|
*/
|
||
|
class DbalMigrator implements Migrator
|
||
|
{
|
||
|
private Stopwatch $stopwatch;
|
||
|
|
||
|
private LoggerInterface $logger;
|
||
|
|
||
|
private Executor $executor;
|
||
|
|
||
|
private Connection $connection;
|
||
|
|
||
|
private EventDispatcher $dispatcher;
|
||
|
|
||
|
public function __construct(
|
||
|
Connection $connection,
|
||
|
EventDispatcher $dispatcher,
|
||
|
Executor $executor,
|
||
|
LoggerInterface $logger,
|
||
|
Stopwatch $stopwatch
|
||
|
) {
|
||
|
$this->stopwatch = $stopwatch;
|
||
|
$this->logger = $logger;
|
||
|
$this->executor = $executor;
|
||
|
$this->connection = $connection;
|
||
|
$this->dispatcher = $dispatcher;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array<string, Query[]>
|
||
|
*/
|
||
|
private function executeMigrations(
|
||
|
MigrationPlanList $migrationsPlan,
|
||
|
MigratorConfiguration $migratorConfiguration
|
||
|
): array {
|
||
|
$allOrNothing = $migratorConfiguration->isAllOrNothing();
|
||
|
|
||
|
if ($allOrNothing) {
|
||
|
$this->assertAllMigrationsAreTransactional($migrationsPlan);
|
||
|
$this->connection->beginTransaction();
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
$this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrating, $migrationsPlan, $migratorConfiguration);
|
||
|
|
||
|
$sql = $this->executePlan($migrationsPlan, $migratorConfiguration);
|
||
|
|
||
|
$this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrated, $migrationsPlan, $migratorConfiguration);
|
||
|
} catch (Throwable $e) {
|
||
|
if ($allOrNothing) {
|
||
|
TransactionHelper::rollbackIfInTransaction($this->connection);
|
||
|
}
|
||
|
|
||
|
throw $e;
|
||
|
}
|
||
|
|
||
|
if ($allOrNothing) {
|
||
|
TransactionHelper::commitIfInTransaction($this->connection);
|
||
|
}
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
private function assertAllMigrationsAreTransactional(MigrationPlanList $migrationsPlan): void
|
||
|
{
|
||
|
foreach ($migrationsPlan->getItems() as $plan) {
|
||
|
if (! $plan->getMigration()->isTransactional()) {
|
||
|
throw MigrationConfigurationConflict::migrationIsNotTransactional($plan->getMigration());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array<string, Query[]>
|
||
|
*/
|
||
|
private function executePlan(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array
|
||
|
{
|
||
|
$sql = [];
|
||
|
$time = 0;
|
||
|
|
||
|
foreach ($migrationsPlan->getItems() as $plan) {
|
||
|
$versionExecutionResult = $this->executor->execute($plan, $migratorConfiguration);
|
||
|
|
||
|
// capture the to Schema for the migration so we have the ability to use
|
||
|
// it as the from Schema for the next migration when we are running a dry run
|
||
|
// $toSchema may be null in the case of skipped migrations
|
||
|
if (! $versionExecutionResult->isSkipped()) {
|
||
|
$migratorConfiguration->setFromSchema($versionExecutionResult->getToSchema());
|
||
|
}
|
||
|
|
||
|
$sql[(string) $plan->getVersion()] = $versionExecutionResult->getSql();
|
||
|
$time += $versionExecutionResult->getTime();
|
||
|
}
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array<string, Query[]> $sql
|
||
|
*/
|
||
|
private function endMigrations(
|
||
|
StopwatchEvent $stopwatchEvent,
|
||
|
MigrationPlanList $migrationsPlan,
|
||
|
array $sql
|
||
|
): void {
|
||
|
$stopwatchEvent->stop();
|
||
|
|
||
|
$this->logger->notice(
|
||
|
'finished in {duration}ms, used {memory} memory, {migrations_count} migrations executed, {queries_count} sql queries',
|
||
|
[
|
||
|
'duration' => $stopwatchEvent->getDuration(),
|
||
|
'memory' => BytesFormatter::formatBytes($stopwatchEvent->getMemory()),
|
||
|
'migrations_count' => count($migrationsPlan),
|
||
|
'queries_count' => count($sql, COUNT_RECURSIVE) - count($sql),
|
||
|
]
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array
|
||
|
{
|
||
|
if (count($migrationsPlan) === 0) {
|
||
|
$this->logger->notice('No migrations to execute.');
|
||
|
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
$stopwatchEvent = $this->stopwatch->start('migrate');
|
||
|
|
||
|
$sql = $this->executeMigrations($migrationsPlan, $migratorConfiguration);
|
||
|
|
||
|
$this->endMigrations($stopwatchEvent, $migrationsPlan, $sql);
|
||
|
|
||
|
return $sql;
|
||
|
}
|
||
|
}
|