Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
83 / 83
90.91% covered (success)
90.91%
40 / 44
60.00% covered (warning)
60.00%
18 / 30
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Executor
100.00% covered (success)
100.00%
83 / 83
90.91% covered (success)
90.91%
40 / 44
60.00% covered (warning)
60.00%
18 / 30
100.00% covered (success)
100.00%
8 / 8
49.22
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
 migrate
100.00% covered (success)
100.00%
12 / 12
84.62% covered (warning)
84.62%
11 / 13
45.45% covered (danger)
45.45%
5 / 11
100.00% covered (success)
100.00%
1 / 1
11.84
 migrateSQL
100.00% covered (success)
100.00%
5 / 5
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
 migrateCompiledSQL
100.00% covered (success)
100.00%
8 / 8
83.33% covered (warning)
83.33%
5 / 6
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
2.50
 migrateTPQL
100.00% covered (success)
100.00%
28 / 28
91.67% covered (success)
91.67%
11 / 12
33.33% covered (danger)
33.33%
2 / 6
100.00% covered (success)
100.00%
1 / 1
8.74
 migratePHP
100.00% covered (success)
100.00%
8 / 8
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
 showEmptyMessage
100.00% covered (success)
100.00%
5 / 5
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
 showMessage
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Quma\Migrations;
6
7use Celema\Console\Io;
8use Celema\Quma\Environment;
9use RuntimeException;
10use Throwable;
11
12final readonly class Executor
13{
14    public const string STARTED = 'start';
15    public const string ERROR = 'error';
16    public const string WARNING = 'warning';
17    public const string SUCCESS = 'success';
18
19    public function __construct(
20        private Environment $env,
21        private Log $log,
22        private PhpLoader $phpLoader,
23        private Io $io,
24    ) {}
25
26    public function migrate(string $namespace, string $migration, bool $showStacktrace): string
27    {
28        $script = file_get_contents($migration);
29
30        if ($script === false) {
31            $this->showMessage($migration, new RuntimeException('Could not read migration file'));
32
33            return self::ERROR;
34        }
35
36        if (trim($script) === '') {
37            $this->showEmptyMessage($migration);
38
39            return self::WARNING;
40        }
41
42        return match (pathinfo($migration, PATHINFO_EXTENSION)) {
43            'sql' => $this->migrateSQL($namespace, $migration, $script, $showStacktrace),
44            'tpql' => $this->migrateTPQL($namespace, $migration, $showStacktrace),
45            'php' => $this->migratePHP($namespace, $migration, $showStacktrace),
46        };
47    }
48
49    private function migrateSQL(
50        string $namespace,
51        string $migration,
52        string $script,
53        bool $showStacktrace,
54    ): string {
55        try {
56            $script = $this->env->conn->config->placeholders?->compileSql($script, $migration) ?? $script;
57
58            return $this->migrateCompiledSQL($namespace, $migration, $script);
59        } catch (Throwable $e) {
60            $this->showMessage($migration, $e, $showStacktrace);
61
62            return self::ERROR;
63        }
64    }
65
66    private function migrateCompiledSQL(
67        string $namespace,
68        string $migration,
69        string $script,
70    ): string {
71        if (trim($script) === '') {
72            $this->showEmptyMessage($migration);
73
74            return self::WARNING;
75        }
76
77        $db = $this->env->db;
78        $db->execute($script)->run();
79        $this->log->record($db, $namespace, $migration);
80        $this->showMessage($migration);
81
82        return self::SUCCESS;
83    }
84
85    private function migrateTPQL(
86        string $namespace,
87        string $migration,
88        bool $showStacktrace,
89    ): string {
90        try {
91            $db = $this->env->db;
92            $conn = $this->env->conn;
93            $context = [
94                'driver' => $db->getPdoDriver(),
95                'db' => $db,
96                'conn' => $conn,
97            ];
98
99            $executeTemplate = static function (
100                string $templatePath,
101                array $context,
102            ): void {
103                extract($context, EXTR_SKIP);
104
105                /** @psalm-suppress UnresolvableInclude */
106                require $templatePath;
107            };
108
109            ob_start();
110            $script = '';
111
112            try {
113                $executeTemplate($migration, $context);
114                $script = ob_get_contents();
115            } finally {
116                ob_end_clean();
117            }
118
119            if (!is_string($script)) {
120                // Defensive guard for an impossible false from ob_get_contents() after ob_start().
121                $script = ''; // @codeCoverageIgnore
122            }
123
124            $script = $conn->config->placeholders?->compileSql($script, $migration) ?? $script;
125
126            if (trim($script) === '') {
127                $this->showEmptyMessage($migration);
128
129                return self::WARNING;
130            }
131
132            return $this->migrateCompiledSQL($namespace, $migration, $script);
133        } catch (Throwable $e) {
134            $this->showMessage($migration, $e, $showStacktrace);
135
136            return self::ERROR;
137        }
138    }
139
140    private function migratePHP(
141        string $namespace,
142        string $migration,
143        bool $showStacktrace,
144    ): string {
145        try {
146            $migrationObject = $this->phpLoader->load($migration);
147            $migrationObject->run($this->env);
148            $this->log->record($this->env->db, $namespace, $migration);
149            $this->showMessage($migration);
150
151            return self::SUCCESS;
152        } catch (Throwable $e) {
153            $this->showMessage($migration, $e, $showStacktrace);
154
155            return self::ERROR;
156        }
157    }
158
159    private function showEmptyMessage(string $migration): void
160    {
161        $this->io->echolnErr(
162            "<yellow>Warning</yellow>: Migration '<bright-yellow>"
163            . basename($migration)
164            . "</bright-yellow>' is empty. Skipped",
165        );
166    }
167
168    private function showMessage(
169        string $migration,
170        ?Throwable $e = null,
171        bool $showStacktrace = false,
172    ): void {
173        $io = $this->io;
174
175        if ($e) {
176            $io->echolnErr(
177                "<bright-red>Error</bright-red>: while working on migration '<bright-yellow>"
178                . basename($migration)
179                . "</bright-yellow>'",
180            );
181            $io->echolnErr($io->escape($e->getMessage()));
182
183            if ($showStacktrace) {
184                $io->echolnErr($io->escape($e->getTraceAsString()));
185            }
186
187            return;
188        }
189
190        $io->echoln(
191            "<bright-green>Success</bright-green>: Migration '<bright-yellow>"
192            . basename($migration)
193            . "</bright-yellow>' successfully applied",
194        );
195    }
196}

Branches

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

Executor->__construct
20        private Environment $env,
21        private Log $log,
22        private PhpLoader $phpLoader,
23        private Io $io,
24    ) {}
Executor->migrate
26    public function migrate(string $namespace, string $migration, bool $showStacktrace): string
27    {
28        $script = file_get_contents($migration);
29
30        if ($script === false) {
31            $this->showMessage($migration, new RuntimeException('Could not read migration file'));
32
33            return self::ERROR;
36        if (trim($script) === '') {
36        if (trim($script) === '') {
36        if (trim($script) === '') {
36        if (trim($script) === '') {
37            $this->showEmptyMessage($migration);
38
39            return self::WARNING;
42        return match (pathinfo($migration, PATHINFO_EXTENSION)) {
42        return match (pathinfo($migration, PATHINFO_EXTENSION)) {
43            'sql' => $this->migrateSQL($namespace, $migration, $script, $showStacktrace),
44            'tpql' => $this->migrateTPQL($namespace, $migration, $showStacktrace),
45            'php' => $this->migratePHP($namespace, $migration, $showStacktrace),
45            'php' => $this->migratePHP($namespace, $migration, $showStacktrace),
46        };
47    }
Executor->migrateCompiledSQL
67        string $namespace,
68        string $migration,
69        string $script,
70    ): string {
71        if (trim($script) === '') {
71        if (trim($script) === '') {
71        if (trim($script) === '') {
71        if (trim($script) === '') {
72            $this->showEmptyMessage($migration);
73
74            return self::WARNING;
77        $db = $this->env->db;
78        $db->execute($script)->run();
79        $this->log->record($db, $namespace, $migration);
80        $this->showMessage($migration);
81
82        return self::SUCCESS;
83    }
Executor->migratePHP
141        string $namespace,
142        string $migration,
143        bool $showStacktrace,
144    ): string {
145        try {
146            $migrationObject = $this->phpLoader->load($migration);
147            $migrationObject->run($this->env);
148            $this->log->record($this->env->db, $namespace, $migration);
149            $this->showMessage($migration);
150
151            return self::SUCCESS;
152        } catch (Throwable $e) {
153            $this->showMessage($migration, $e, $showStacktrace);
154
155            return self::ERROR;
156        }
157    }
Executor->migrateSQL
50        string $namespace,
51        string $migration,
52        string $script,
53        bool $showStacktrace,
54    ): string {
55        try {
56            $script = $this->env->conn->config->placeholders?->compileSql($script, $migration) ?? $script;
57
58            return $this->migrateCompiledSQL($namespace, $migration, $script);
59        } catch (Throwable $e) {
60            $this->showMessage($migration, $e, $showStacktrace);
61
62            return self::ERROR;
63        }
64    }
Executor->migrateTPQL
86        string $namespace,
87        string $migration,
88        bool $showStacktrace,
89    ): string {
90        try {
91            $db = $this->env->db;
92            $conn = $this->env->conn;
93            $context = [
94                'driver' => $db->getPdoDriver(),
95                'db' => $db,
96                'conn' => $conn,
97            ];
98
99            $executeTemplate = static function (
100                string $templatePath,
101                array $context,
102            ): void {
103                extract($context, EXTR_SKIP);
104
105                /** @psalm-suppress UnresolvableInclude */
106                require $templatePath;
107            };
108
109            ob_start();
110            $script = '';
111
112            try {
113                $executeTemplate($migration, $context);
114                $script = ob_get_contents();
115            } finally {
115            } finally {
116                ob_end_clean();
119            if (!is_string($script)) {
124            $script = $conn->config->placeholders?->compileSql($script, $migration) ?? $script;
125
126            if (trim($script) === '') {
126            if (trim($script) === '') {
126            if (trim($script) === '') {
126            if (trim($script) === '') {
127                $this->showEmptyMessage($migration);
128
129                return self::WARNING;
132            return $this->migrateCompiledSQL($namespace, $migration, $script);
133        } catch (Throwable $e) {
134            $this->showMessage($migration, $e, $showStacktrace);
135
136            return self::ERROR;
137        }
138    }
Executor->showEmptyMessage
159    private function showEmptyMessage(string $migration): void
160    {
161        $this->io->echolnErr(
162            "<yellow>Warning</yellow>: Migration '<bright-yellow>"
163            . basename($migration)
164            . "</bright-yellow>' is empty. Skipped",
165        );
166    }
Executor->showMessage
169        string $migration,
170        ?Throwable $e = null,
171        bool $showStacktrace = false,
172    ): void {
173        $io = $this->io;
174
175        if ($e) {
176            $io->echolnErr(
177                "<bright-red>Error</bright-red>: while working on migration '<bright-yellow>"
178                . basename($migration)
179                . "</bright-yellow>'",
180            );
181            $io->echolnErr($io->escape($e->getMessage()));
182
183            if ($showStacktrace) {
184                $io->echolnErr($io->escape($e->getTraceAsString()));
185            }
186
187            return;
187            return;
190        $io->echoln(
191            "<bright-green>Success</bright-green>: Migration '<bright-yellow>"
192            . basename($migration)
193            . "</bright-yellow>' successfully applied",
194        );
195    }
{closure:/workspace/celema/quma/src/Migrations/Executor.php:99-107}
100                string $templatePath,
101                array $context,
102            ): void {
103                extract($context, EXTR_SKIP);
104
105                /** @psalm-suppress UnresolvableInclude */
106                require $templatePath;
107            };