Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
33 / 33
70.83% covered (warning)
70.83%
17 / 24
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Runner
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
33 / 33
70.83% covered (warning)
70.83%
17 / 24
100.00% covered (success)
100.00%
4 / 4
29.92
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
13 / 13
42.86% covered (danger)
42.86%
3 / 7
100.00% covered (success)
100.00%
1 / 1
24.11
 finish
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
16 / 16
78.57% covered (warning)
78.57%
11 / 14
100.00% covered (success)
100.00%
1 / 1
8.63
 begin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Quma\Migrations;
6
7use Celema\Console\Io;
8use Celema\Quma\Database;
9use Celema\Quma\Environment;
10
11final readonly class Runner
12{
13    // Internal wiring, constructed by the Migrations command only.
14    // @mago-expect lint:excessive-parameter-list
15    public function __construct(
16        private Environment $env,
17        private DriverPolicy $driverPolicy,
18        private Planner $planner,
19        private Log $log,
20        private Executor $executor,
21        private Io $io,
22    ) {}
23
24    /** @param list<string> $migrations */
25    public function run(string $namespace, array $migrations, RunOptions $options): int
26    {
27        $db = $this->env->db;
28
29        $this->begin($db);
30
31        if (!$options->tableExists) {
32            $result = ($options->createMigrationsTable)();
33
34            // @codeCoverageIgnoreStart
35            if ($result !== 0) {
36                if ($this->driverPolicy->supportsTransactions()) {
37                    $db->rollback();
38                }
39
40                return $result;
41            } // @codeCoverageIgnoreEnd
42        }
43
44        $appliedMigrations = $this->log->applied($db);
45        $result = Executor::STARTED;
46        $numApplied = 0;
47
48        foreach ($migrations as $migration) {
49            assert($migration !== '', 'Migration path must be a non-empty string.');
50
51            $migrationId = $this->planner->migrationId($namespace, $migration);
52
53            if (in_array($migrationId, $appliedMigrations, strict: true)) {
54                continue;
55            }
56
57            if (!$this->driverPolicy->supportsMigration($migration)) {
58                continue;
59            }
60
61            $result = $this->executor->migrate($namespace, $migration, $options->showStacktrace);
62
63            if ($result === Executor::ERROR) {
64                break;
65            }
66
67            if ($result === Executor::SUCCESS) {
68                $numApplied++;
69            }
70        }
71
72        return $this->finish($db, $result, $options->apply, $numApplied);
73    }
74
75    public function finish(
76        Database $db,
77        string $result,
78        bool $apply,
79        int $numApplied,
80    ): int {
81        $io = $this->io;
82        $plural = $numApplied > 1 ? 's' : '';
83
84        if ($this->driverPolicy->supportsTransactions()) {
85            if ($result === Executor::ERROR) {
86                $db->rollback();
87                $io->echolnErr("\nDue to errors no migrations applied");
88
89                return 1;
90            }
91
92            if ($numApplied === 0) {
93                $db->rollback();
94                $io->echoln("\nNo migrations applied");
95
96                return 0;
97            }
98
99            if ($apply) {
100                $db->commit();
101                $io->echoln("\n{$numApplied} migration{$plural} successfully applied");
102
103                return 0;
104            }
105            $io->echoln("\n<bright-red>Notice</bright-red>: Test run only");
106            $io->echo("Rolled back {$numApplied} migration{$plural}");
107            $io->echoln('Use --apply to commit them');
108            $db->rollback();
109
110            return 0;
111        }
112
113        if ($result === Executor::ERROR) {
114            $io->echolnErr("\n{$numApplied} migration{$plural} applied until the error occured");
115
116            return 1;
117        }
118
119        if ($numApplied > 0) {
120            $io->echoln("\n{$numApplied} migration{$plural} successfully applied");
121
122            return 0;
123        }
124
125        $io->echoln("\nNo migrations applied");
126
127        return 0;
128    }
129
130    private function begin(Database $db): void
131    {
132        if ($this->driverPolicy->supportsTransactions()) {
133            $db->begin();
134        }
135    }
136}