Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
106 / 106
98.63% covered (success)
98.63%
72 / 73
3.36% covered (danger)
3.36%
32 / 953
93.33% covered (success)
93.33%
14 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Migrations
100.00% covered (success)
100.00%
106 / 106
98.63% covered (success)
98.63%
72 / 73
3.36% covered (danger)
3.36%
32 / 953
100.00% covered (success)
100.00%
15 / 15
1634.20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
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
 __invoke
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
33 / 33
1.19% covered (danger)
1.19%
11 / 926
100.00% covered (success)
100.00%
1 / 1
295.82
 migrate
100.00% covered (success)
100.00%
10 / 10
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
 confirmTestRunForPending
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
6 / 6
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
 migrationsForNamespace
100.00% covered (success)
100.00%
20 / 20
93.33% covered (success)
93.33%
14 / 15
85.71% covered (warning)
85.71%
6 / 7
100.00% covered (success)
100.00%
1 / 1
7.14
 migrationIdsAreUnique
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
7 / 7
40.00% covered (danger)
40.00%
2 / 5
100.00% covered (success)
100.00%
1 / 1
4.94
 createMigrationsTable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 supportsTransactions
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
 driverPolicy
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
 planner
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
 phpLoader
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
 log
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
 plan
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
 executor
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
 runner
100.00% covered (success)
100.00%
8 / 8
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
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Quma\Commands;
6
7use Celema\Console\Args;
8use Celema\Console\Command;
9use Celema\Console\Io;
10use Celema\Console\Opt;
11use Celema\Quma\Connection;
12use Celema\Quma\Contract;
13use Celema\Quma\Environment;
14use Celema\Quma\Migrations\DriverPolicy;
15use Celema\Quma\Migrations\Executor;
16use Celema\Quma\Migrations\Log;
17use Celema\Quma\Migrations\MetadataTable;
18use Celema\Quma\Migrations\PhpLoader;
19use Celema\Quma\Migrations\Plan;
20use Celema\Quma\Migrations\Planner;
21use Celema\Quma\Migrations\Runner;
22use Celema\Quma\Migrations\RunOptions;
23use Celema\Quma\Migrations\TestRunConfirmation;
24
25#[Command('db:migrations', 'Apply missing database migrations', group: 'Database')]
26#[Opt('--apply', 'Apply the pending migrations instead of only planning them')]
27#[Opt(
28    '--test-run',
29    'Run pending migrations inside a transaction and roll back (sqlite/pgsql only)',
30)]
31#[Opt('--namespace', 'Migration namespace to run', value: 'name')]
32#[Opt('--conn', 'Connection to use', value: 'name')]
33#[Opt('--stacktrace', 'Show stack traces for failing migrations')]
34#[Opt('--yes', 'Skip the test-run confirmation prompt')]
35final class Migrations
36{
37    protected readonly Environment $env;
38    protected readonly ?Contract\MigrationFactory $migrationFactory;
39
40    /** @psalm-suppress PropertyNotSetInConstructor Assigned first thing in __invoke() */
41    protected Io $io;
42
43    /** @param array<non-empty-string, Connection>|Connection $conn */
44    public function __construct(
45        array|Connection $conn,
46        array $options = [],
47        ?Contract\MigrationFactory $migrationFactory = null,
48    ) {
49        $this->env = new Environment($conn, $options);
50        $this->migrationFactory = $migrationFactory;
51    }
52
53    public function __invoke(Args $args, Io $io): int
54    {
55        $this->io = $io;
56        $env = $this->env;
57        $namespace = $args->opt('--namespace', '');
58        $showStacktrace = $args->has('--stacktrace');
59        $apply = $args->has('--apply');
60        $testRun = $args->has('--test-run');
61        $yes = $args->has('--yes');
62        $driverSupported = $this->driverPolicy()->isKnown();
63
64        if ($apply && $testRun) {
65            $io->echolnErr(
66                '<bright-red>Error</bright-red>: Options --apply and --test-run cannot be used together.',
67            );
68
69            return 1;
70        }
71
72        if ($testRun && (!$driverSupported || !$this->supportsTransactions())) {
73            $io->echolnErr(
74                '<bright-red>Error</bright-red>: Test runs are only supported for transactional drivers: sqlite and pgsql.',
75            );
76            $io->echolnErr(
77                'MySQL migrations are plan-only without --apply because DDL statements can cause implicit commits.',
78            );
79
80            return 1;
81        }
82
83        $migrations = $this->migrationsForNamespace($namespace);
84
85        if ($migrations === false) {
86            return 1;
87        }
88
89        $migrationNamespace = $namespace !== '' ? $namespace : 'default';
90        $tableExists = $driverSupported && $env->checkIfMigrationsTableExists($env->db);
91
92        if (!$apply && !$testRun) {
93            return $this->plan()->show($migrationNamespace, $migrations, $tableExists);
94        }
95
96        if (
97            !$apply && !$this->confirmTestRunForPending($migrationNamespace, $migrations, $tableExists, $yes)
98        ) {
99            return 1;
100        }
101
102        if ($driverSupported && !$tableExists && !$this->supportsTransactions()) {
103            $result = $this->createMigrationsTable();
104
105            if ($result !== 0) {
106                // Requires simulating failing metadata table creation.
107                return $result; // @codeCoverageIgnore
108            }
109
110            $tableExists = true;
111        }
112
113        return $this->migrate(
114            $migrationNamespace,
115            $migrations,
116            $showStacktrace,
117            $apply,
118            $tableExists,
119        );
120    }
121
122    /** @param list<string> $migrations */
123    protected function migrate(
124        string $namespace,
125        array $migrations,
126        bool $showStacktrace,
127        bool $apply,
128        bool $tableExists,
129    ): int {
130        return $this->runner()->run(
131            $namespace,
132            $migrations,
133            new RunOptions(
134                $showStacktrace,
135                $apply,
136                $tableExists,
137                $this->createMigrationsTable(...),
138            ),
139        );
140    }
141
142    /**
143     * @param list<string> $migrations
144     */
145    protected function confirmTestRunForPending(
146        string $namespace,
147        array $migrations,
148        bool $tableExists,
149        bool $yes,
150    ): bool {
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
152        $pendingMigrations = $this->planner()->pendingMigrations(
153            $namespace,
154            $migrations,
155            $appliedMigrations,
156        );
157
158        if (count($pendingMigrations) === 0) {
159            return true;
160        }
161
162        return new TestRunConfirmation()->confirm($this->io, $yes);
163    }
164
165    /**
166     * @return list<string>|false
167     */
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
173            $this->io->error('No migration directories defined in configuration');
174
175            return false;
176        }
177
178        if ($namespace) {
179            if (!array_key_exists($namespace, $migrationNamespaces)) {
180                $this->io->error("Migration namespace '{$namespace}' does not exist");
181
182                return false;
183            }
184
185            $migrations = $migrationNamespaces[$namespace];
186
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
188        }
189
190        if (!array_key_exists('default', $migrationNamespaces)) {
191            $this->io->error("Migration namespace 'default' does not exist");
192            $this->io->info(
193                'If you have defined namespaced migrations, you must either provide a namespace using the '
194                . "`--namespace` flag when running this command, or define a namespace named 'default' which "
195                . 'will be used when no namespace is provided.',
196            );
197
198            return false;
199        }
200
201        $migrations = $migrationNamespaces['default'];
202
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
204    }
205
206    /** @param list<string> $migrations */
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
221
222    protected function createMigrationsTable(): int
223    {
224        $result = new MetadataTable($this->env, $this->io)->create($this->env->db);
225
226        if ($result === 0) {
227            return 0;
228        }
229
230        // Would require simulating failing metadata table creation.
231        // @codeCoverageIgnoreStart
232        $this->io->error('Migration table could not be created.');
233
234        return $result;
235
236        // @codeCoverageIgnoreEnd
237    }
238
239    protected function supportsTransactions(): bool
240    {
241        return $this->driverPolicy()->supportsTransactions();
242    }
243
244    private function driverPolicy(): DriverPolicy
245    {
246        return new DriverPolicy($this->env->driver);
247    }
248
249    private function planner(): Planner
250    {
251        return new Planner($this->driverPolicy());
252    }
253
254    private function phpLoader(): PhpLoader
255    {
256        return new PhpLoader($this->env, $this->migrationFactory);
257    }
258
259    private function log(): Log
260    {
261        return new Log($this->env, $this->planner());
262    }
263
264    private function plan(): Plan
265    {
266        return new Plan($this->env, $this->planner(), $this->log(), $this->io);
267    }
268
269    private function executor(): Executor
270    {
271        return new Executor($this->env, $this->log(), $this->phpLoader(), $this->io);
272    }
273
274    private function runner(): Runner
275    {
276        return new Runner(
277            $this->env,
278            $this->driverPolicy(),
279            $this->planner(),
280            $this->log(),
281            $this->executor(),
282            $this->io,
283        );
284    }
285}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

Migrations->__construct
45        array|Connection $conn,
46        array $options = [],
47        ?Contract\MigrationFactory $migrationFactory = null,
48    ) {
49        $this->env = new Environment($conn, $options);
50        $this->migrationFactory = $migrationFactory;
51    }
Migrations->confirmTestRunForPending
146        string $namespace,
147        array $migrations,
148        bool $tableExists,
149        bool $yes,
150    ): bool {
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
152        $pendingMigrations = $this->planner()->pendingMigrations(
153            $namespace,
154            $migrations,
155            $appliedMigrations,
156        );
157
158        if (count($pendingMigrations) === 0) {
 
159            return true;
146        string $namespace,
147        array $migrations,
148        bool $tableExists,
149        bool $yes,
150    ): bool {
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
152        $pendingMigrations = $this->planner()->pendingMigrations(
153            $namespace,
154            $migrations,
155            $appliedMigrations,
156        );
157
158        if (count($pendingMigrations) === 0) {
 
162        return new TestRunConfirmation()->confirm($this->io, $yes);
163    }
146        string $namespace,
147        array $migrations,
148        bool $tableExists,
149        bool $yes,
150    ): bool {
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
152        $pendingMigrations = $this->planner()->pendingMigrations(
153            $namespace,
154            $migrations,
155            $appliedMigrations,
156        );
157
158        if (count($pendingMigrations) === 0) {
 
159            return true;
146        string $namespace,
147        array $migrations,
148        bool $tableExists,
149        bool $yes,
150    ): bool {
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
 
151        $appliedMigrations = $tableExists ? $this->log()->applied($this->env->db) : [];
152        $pendingMigrations = $this->planner()->pendingMigrations(
153            $namespace,
154            $migrations,
155            $appliedMigrations,
156        );
157
158        if (count($pendingMigrations) === 0) {
 
162        return new TestRunConfirmation()->confirm($this->io, $yes);
163    }
Migrations->createMigrationsTable
224        $result = new MetadataTable($this->env, $this->io)->create($this->env->db);
225
226        if ($result === 0) {
 
227            return 0;
Migrations->driverPolicy
246        return new DriverPolicy($this->env->driver);
247    }
Migrations->executor
271        return new Executor($this->env, $this->log(), $this->phpLoader(), $this->io);
272    }
Migrations->log
261        return new Log($this->env, $this->planner());
262    }
Migrations->migrate
124        string $namespace,
125        array $migrations,
126        bool $showStacktrace,
127        bool $apply,
128        bool $tableExists,
129    ): int {
130        return $this->runner()->run(
131            $namespace,
132            $migrations,
133            new RunOptions(
134                $showStacktrace,
135                $apply,
136                $tableExists,
137                $this->createMigrationsTable(...),
138            ),
139        );
140    }
Migrations->migrationIdsAreUnique
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
214            foreach ($paths as $path) {
 
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
 
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
207    protected function migrationIdsAreUnique(string $namespace, array $migrations): bool
208    {
209        $duplicates = $this->planner()->duplicateMigrationIds($namespace, $migrations);
210
211        foreach ($duplicates as $id => $paths) {
 
211        foreach ($duplicates as $id => $paths) {
212            $this->io->error("Duplicate migration id '{$id}' in namespace '{$namespace}'");
213
214            foreach ($paths as $path) {
215                $this->io->echolnErr("  - {$path}");
216            }
217        }
218
219        return count($duplicates) === 0;
220    }
Migrations->migrationsForNamespace
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
173            $this->io->error('No migration directories defined in configuration');
174
175            return false;
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
179            if (!array_key_exists($namespace, $migrationNamespaces)) {
 
180                $this->io->error("Migration namespace '{$namespace}' does not exist");
181
182                return false;
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
179            if (!array_key_exists($namespace, $migrationNamespaces)) {
 
185            $migrations = $migrationNamespaces[$namespace];
186
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
 
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
 
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
179            if (!array_key_exists($namespace, $migrationNamespaces)) {
 
185            $migrations = $migrationNamespaces[$namespace];
186
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
 
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
 
187            return $this->migrationIdsAreUnique($namespace, $migrations) ? $migrations : false;
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
190        if (!array_key_exists('default', $migrationNamespaces)) {
 
191            $this->io->error("Migration namespace 'default' does not exist");
192            $this->io->info(
193                'If you have defined namespaced migrations, you must either provide a namespace using the '
194                . "`--namespace` flag when running this command, or define a namespace named 'default' which "
195                . 'will be used when no namespace is provided.',
196            );
197
198            return false;
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
190        if (!array_key_exists('default', $migrationNamespaces)) {
 
201        $migrations = $migrationNamespaces['default'];
202
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
 
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
 
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
204    }
168    protected function migrationsForNamespace(string $namespace): array|false
169    {
170        $migrationNamespaces = $this->env->getMigrations();
171
172        if ($migrationNamespaces === false) {
 
178        if ($namespace) {
 
190        if (!array_key_exists('default', $migrationNamespaces)) {
 
201        $migrations = $migrationNamespaces['default'];
202
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
 
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
 
203        return $this->migrationIdsAreUnique('default', $migrations) ? $migrations : false;
204    }
Migrations->phpLoader
256        return new PhpLoader($this->env, $this->migrationFactory);
257    }
Migrations->plan
266        return new Plan($this->env, $this->planner(), $this->log(), $this->io);
267    }
Migrations->planner
251        return new Planner($this->driverPolicy());
252    }
Migrations->runner
276        return new Runner(
277            $this->env,
278            $this->driverPolicy(),
279            $this->planner(),
280            $this->log(),
281            $this->executor(),
282            $this->io,
283        );
284    }
Migrations->supportsTransactions
241        return $this->driverPolicy()->supportsTransactions();
242    }
createMigrationsTable
224        $result = new MetadataTable($this->env, $this->io)->create($this->env->db);
225
226        if ($result === 0) {
 
227            return 0;