DbalMigrator.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\Migrations;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\Migrations\Metadata\MigrationPlanList;
  6. use Doctrine\Migrations\Query\Query;
  7. use Doctrine\Migrations\Tools\BytesFormatter;
  8. use Doctrine\Migrations\Tools\TransactionHelper;
  9. use Doctrine\Migrations\Version\Executor;
  10. use Psr\Log\LoggerInterface;
  11. use Symfony\Component\Stopwatch\Stopwatch;
  12. use Symfony\Component\Stopwatch\StopwatchEvent;
  13. use Throwable;
  14. use function count;
  15. use const COUNT_RECURSIVE;
  16. /**
  17. * The DbalMigrator class is responsible for generating and executing the SQL for a migration.
  18. *
  19. * @internal
  20. */
  21. class DbalMigrator implements Migrator
  22. {
  23. /** @var Stopwatch */
  24. private $stopwatch;
  25. /** @var LoggerInterface */
  26. private $logger;
  27. /** @var Executor */
  28. private $executor;
  29. /** @var Connection */
  30. private $connection;
  31. /** @var EventDispatcher */
  32. private $dispatcher;
  33. public function __construct(
  34. Connection $connection,
  35. EventDispatcher $dispatcher,
  36. Executor $executor,
  37. LoggerInterface $logger,
  38. Stopwatch $stopwatch
  39. ) {
  40. $this->stopwatch = $stopwatch;
  41. $this->logger = $logger;
  42. $this->executor = $executor;
  43. $this->connection = $connection;
  44. $this->dispatcher = $dispatcher;
  45. }
  46. /**
  47. * @return array<string, Query[]>
  48. */
  49. private function executeMigrations(
  50. MigrationPlanList $migrationsPlan,
  51. MigratorConfiguration $migratorConfiguration
  52. ): array {
  53. $allOrNothing = $migratorConfiguration->isAllOrNothing();
  54. if ($allOrNothing) {
  55. $this->connection->beginTransaction();
  56. }
  57. try {
  58. $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrating, $migrationsPlan, $migratorConfiguration);
  59. $sql = $this->executePlan($migrationsPlan, $migratorConfiguration);
  60. $this->dispatcher->dispatchMigrationEvent(Events::onMigrationsMigrated, $migrationsPlan, $migratorConfiguration);
  61. } catch (Throwable $e) {
  62. if ($allOrNothing) {
  63. $this->connection->rollBack();
  64. }
  65. throw $e;
  66. }
  67. if ($allOrNothing) {
  68. TransactionHelper::commitIfInTransaction($this->connection);
  69. }
  70. return $sql;
  71. }
  72. /**
  73. * @return array<string, Query[]>
  74. */
  75. private function executePlan(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array
  76. {
  77. $sql = [];
  78. $time = 0;
  79. foreach ($migrationsPlan->getItems() as $plan) {
  80. $versionExecutionResult = $this->executor->execute($plan, $migratorConfiguration);
  81. // capture the to Schema for the migration so we have the ability to use
  82. // it as the from Schema for the next migration when we are running a dry run
  83. // $toSchema may be null in the case of skipped migrations
  84. if (! $versionExecutionResult->isSkipped()) {
  85. $migratorConfiguration->setFromSchema($versionExecutionResult->getToSchema());
  86. }
  87. $sql[(string) $plan->getVersion()] = $versionExecutionResult->getSql();
  88. $time += $versionExecutionResult->getTime();
  89. }
  90. return $sql;
  91. }
  92. /**
  93. * @param array<string, Query[]> $sql
  94. */
  95. private function endMigrations(
  96. StopwatchEvent $stopwatchEvent,
  97. MigrationPlanList $migrationsPlan,
  98. array $sql
  99. ): void {
  100. $stopwatchEvent->stop();
  101. $this->logger->notice(
  102. 'finished in {duration}ms, used {memory} memory, {migrations_count} migrations executed, {queries_count} sql queries',
  103. [
  104. 'duration' => $stopwatchEvent->getDuration(),
  105. 'memory' => BytesFormatter::formatBytes($stopwatchEvent->getMemory()),
  106. 'migrations_count' => count($migrationsPlan),
  107. 'queries_count' => count($sql, COUNT_RECURSIVE) - count($sql),
  108. ]
  109. );
  110. }
  111. /**
  112. * {@inheritDoc}
  113. */
  114. public function migrate(MigrationPlanList $migrationsPlan, MigratorConfiguration $migratorConfiguration): array
  115. {
  116. if (count($migrationsPlan) === 0) {
  117. $this->logger->notice('No migrations to execute.');
  118. return [];
  119. }
  120. $stopwatchEvent = $this->stopwatch->start('migrate');
  121. $sql = $this->executeMigrations($migrationsPlan, $migratorConfiguration);
  122. $this->endMigrations($stopwatchEvent, $migrationsPlan, $sql);
  123. return $sql;
  124. }
  125. }