Passed
Pull Request — master (#683)
by Jonathan
02:21
created

Migrator::executeMigration()   B

Complexity

Conditions 6
Paths 36

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 6

Importance

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