Passed
Pull Request — master (#739)
by Michael
02:38
created

Migrator::endMigration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 27
ccs 15
cts 15
cp 1
crap 1
rs 9.488
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations;
6
7
use Doctrine\Migrations\Configuration\Configuration;
8
use Doctrine\Migrations\Exception\MigrationException;
9
use Doctrine\Migrations\Exception\NoMigrationsToExecute;
10
use Doctrine\Migrations\Exception\UnknownMigrationVersion;
11
use Doctrine\Migrations\Tools\BytesFormatter;
12
use Doctrine\Migrations\Version\Direction;
13
use Doctrine\Migrations\Version\Version;
14
use Symfony\Component\Stopwatch\StopwatchEvent;
15
use Throwable;
16
use const COUNT_RECURSIVE;
17
use function count;
18
use function sprintf;
19
20
/**
21
 * The Migrator class is responsible for generating and executing the SQL for a migration.
22
 *
23
 * @internal
24
 */
25
class Migrator
26
{
27
    /** @var Configuration */
28
    private $configuration;
29
30
    /** @var MigrationRepository */
31
    private $migrationRepository;
32
33
    /** @var OutputWriter */
34
    private $outputWriter;
35
36
    /** @var Stopwatch */
37
    private $stopwatch;
38
39 37
    public function __construct(
40
        Configuration $configuration,
41
        MigrationRepository $migrationRepository,
42
        OutputWriter $outputWriter,
43
        Stopwatch $stopwatch
44
    ) {
45 37
        $this->configuration       = $configuration;
46 37
        $this->migrationRepository = $migrationRepository;
47 37
        $this->outputWriter        = $outputWriter;
48 37
        $this->stopwatch           = $stopwatch;
49 37
    }
50
51
    /** @return string[][] */
52 2
    public function getSql(?string $to = null) : array
53
    {
54 2
        $migratorConfiguration = (new MigratorConfiguration())
55 2
            ->setDryRun(true);
56
57 2
        return $this->migrate($to, $migratorConfiguration);
58
    }
59
60 6
    public function writeSqlFile(string $path, ?string $to = null) : bool
61
    {
62 6
        $sql = $this->getSql($to);
63
64 6
        $from = $this->migrationRepository->getCurrentVersion();
65
66 6
        if ($to === null) {
67 1
            $to = $this->migrationRepository->getLatestVersion();
68
        }
69
70 6
        $direction = $from > $to
71 1
            ? Direction::DOWN
72 6
            : Direction::UP;
73
74 6
        $this->outputWriter->write(
75 6
            sprintf("-- Migrating from %s to %s\n", $from, $to)
76
        );
77
78
        /**
79
         * Since the configuration object changes during the creation we cannot inject things
80
         * properly, so I had to violate LoD here (so please, let's find a way to solve it on v2).
81
         */
82 6
        return $this->configuration
83 6
            ->getQueryWriter()
84 6
            ->write($path, $direction, $sql);
85
    }
86
87
    /**
88
     * @throws MigrationException
89
     *
90
     * @return string[][]
91
     */
92 31
    public function migrate(
93
        ?string $to = null,
94
        ?MigratorConfiguration $migratorConfiguration = null
95
    ) : array {
96 31
        $migratorConfiguration = $migratorConfiguration ?? new MigratorConfiguration();
97 31
        $dryRun                = $migratorConfiguration->isDryRun();
98
99 31
        if ($to === null) {
100 14
            $to = $this->migrationRepository->getLatestVersion();
101
        }
102
103 31
        $versions = $this->migrationRepository->getMigrations();
104
105 31
        if (! isset($versions[$to]) && $to > 0) {
106 1
            throw UnknownMigrationVersion::new($to);
107
        }
108
109 30
        $from = $this->migrationRepository->getCurrentVersion();
110
111 30
        $direction = $this->calculateDirection($from, $to);
112
113 30
        $migrationsToExecute = $this->configuration
114 30
            ->getMigrationsToExecute($direction, $to);
115
116
        /**
117
         * If
118
         *  there are no migrations to execute
119
         *  and there are migrations,
120
         *  and the migration from and to are the same
121
         * means we are already at the destination return an empty array()
122
         * to signify that there is nothing left to do.
123
         */
124 30
        if ($from === $to && count($migrationsToExecute) === 0 && count($versions) !== 0) {
125 2
            return $this->noMigrations();
126
        }
127
128 29
        $output  = $dryRun ? 'Executing dry run of migration' : 'Migrating';
129 29
        $output .= ' <info>%s</info> to <comment>%s</comment> from <comment>%s</comment>';
130
131 29
        $this->outputWriter->write(sprintf($output, $direction, $to, $from));
132
133
        /**
134
         * If there are no migrations to execute throw an exception.
135
         */
136 29
        if (count($migrationsToExecute) === 0 && ! $migratorConfiguration->getNoMigrationException()) {
137 1
            throw NoMigrationsToExecute::new();
138 28
        } elseif (count($migrationsToExecute) === 0) {
139 1
            return $this->noMigrations();
140
        }
141
142 27
        $stopwatchEvent = $this->stopwatch->start('migrate');
143
144 27
        $sql = $this->executeMigration($migrationsToExecute, $direction, $migratorConfiguration);
145
146 26
        $this->endMigration($stopwatchEvent, $migrationsToExecute, $sql);
147
148 26
        return $sql;
149
    }
150
151
    /**
152
     * @param Version[] $migrationsToExecute
153
     *
154
     * @return string[][]
155
     */
156 27
    private function executeMigration(
157
        array $migrationsToExecute,
158
        string $direction,
159
        MigratorConfiguration $migratorConfiguration
160
    ) : array {
161 27
        $dryRun = $migratorConfiguration->isDryRun();
162
163 27
        $this->configuration->dispatchMigrationEvent(Events::onMigrationsMigrating, $direction, $dryRun);
164
165 27
        $connection = $this->configuration->getConnection();
166
167 27
        $allOrNothing = $migratorConfiguration->isAllOrNothing();
168
169 27
        if ($allOrNothing) {
170 2
            $connection->beginTransaction();
171
        }
172
173
        try {
174 27
            $this->configuration->dispatchMigrationEvent(Events::onMigrationsMigrating, $direction, $dryRun);
175
176 27
            $sql  = [];
177 27
            $time = 0;
178
179 27
            foreach ($migrationsToExecute as $version) {
180 27
                $versionExecutionResult = $version->execute($direction, $migratorConfiguration);
181
182 26
                $sql[$version->getVersion()] = $versionExecutionResult->getSql();
183 26
                $time                       += $versionExecutionResult->getTime();
184
            }
185
186 26
            $this->configuration->dispatchMigrationEvent(Events::onMigrationsMigrated, $direction, $dryRun);
187 1
        } catch (Throwable $e) {
188 1
            if ($allOrNothing) {
189 1
                $connection->rollBack();
190
            }
191
192 1
            throw $e;
193
        }
194
195 26
        if ($allOrNothing) {
196 1
            $connection->commit();
197
        }
198
199 26
        return $sql;
200
    }
201
202
    /**
203
     * @param Version[]  $migrationsToExecute
204
     * @param string[][] $sql
205
     */
206 26
    private function endMigration(
207
        StopwatchEvent $stopwatchEvent,
208
        array $migrationsToExecute,
209
        array $sql
210
    ) : void {
211 26
        $stopwatchEvent->stop();
212
213 26
        $this->outputWriter->write("\n  <comment>------------------------</comment>\n");
214
215 26
        $this->outputWriter->write(sprintf(
216 26
            '  <info>++</info> finished in %sms',
217 26
            $stopwatchEvent->getDuration()
218
        ));
219
220 26
        $this->outputWriter->write(sprintf(
221 26
            '  <info>++</info> used %s memory',
222 26
            BytesFormatter::formatBytes($stopwatchEvent->getMemory())
223
        ));
224
225 26
        $this->outputWriter->write(sprintf(
226 26
            '  <info>++</info> %s migrations executed',
227 26
            count($migrationsToExecute)
228
        ));
229
230 26
        $this->outputWriter->write(sprintf(
231 26
            '  <info>++</info> %s sql queries',
232 26
            count($sql, COUNT_RECURSIVE) - count($sql)
233
        ));
234 26
    }
235
236 30
    private function calculateDirection(string $from, string $to) : string
237
    {
238 30
        return (int) $from > (int) $to ? Direction::DOWN : Direction::UP;
239
    }
240
241
    /** @return string[][] */
242 3
    private function noMigrations() : array
243
    {
244 3
        $this->outputWriter->write('<comment>No migrations to execute.</comment>');
245
246 3
        return [];
247
    }
248
}
249