Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
77 / 77
92.31% covered (success)
92.31%
36 / 39
66.67% covered (warning)
66.67%
18 / 27
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Executor
100.00% covered (success)
100.00%
77 / 77
92.31% covered (success)
92.31%
36 / 39
66.67% covered (warning)
66.67%
18 / 27
100.00% covered (success)
100.00%
8 / 8
34.81
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%
25 / 25
100.00% covered (success)
100.00%
7 / 7
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
3.33
 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%
4 / 4
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%
14 / 14
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 The migration path is only known at run time. */
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            return $this->migrateCompiledSQL($namespace, $migration, $script);
127        } catch (Throwable $e) {
128            $this->showMessage($migration, $e, $showStacktrace);
129
130            return self::ERROR;
131        }
132    }
133
134    private function migratePHP(
135        string $namespace,
136        string $migration,
137        bool $showStacktrace,
138    ): string {
139        try {
140            $migrationObject = $this->phpLoader->load($migration);
141            $migrationObject->run($this->env);
142            $this->log->record($this->env->db, $namespace, $migration);
143            $this->showMessage($migration);
144
145            return self::SUCCESS;
146        } catch (Throwable $e) {
147            $this->showMessage($migration, $e, $showStacktrace);
148
149            return self::ERROR;
150        }
151    }
152
153    private function showEmptyMessage(string $migration): void
154    {
155        $this->io->warn(
156            "Warning: Migration '<bright-yellow>%s</bright-yellow>' is empty. Skipped",
157            basename($migration),
158        );
159    }
160
161    private function showMessage(
162        string $migration,
163        ?Throwable $e = null,
164        bool $showStacktrace = false,
165    ): void {
166        $io = $this->io;
167
168        if ($e) {
169            $io->error(
170                "Error: while working on migration '<bright-yellow>%s</bright-yellow>'",
171                basename($migration),
172            );
173            $io->stderr()->line('%s', $e->getMessage());
174
175            if ($showStacktrace) {
176                $io->stderr()->line('%s', $e->getTraceAsString());
177            }
178
179            return;
180        }
181
182        $io->line(
183            "<bright-green>Success</bright-green>: Migration '<bright-yellow>%s</bright-yellow>' successfully applied",
184            basename($migration),
185        );
186    }
187}

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
135        string $namespace,
136        string $migration,
137        bool $showStacktrace,
138    ): string {
139        try {
140            $migrationObject = $this->phpLoader->load($migration);
141            $migrationObject->run($this->env);
142            $this->log->record($this->env->db, $namespace, $migration);
143            $this->showMessage($migration);
144
145            return self::SUCCESS;
146        } catch (Throwable $e) {
147            $this->showMessage($migration, $e, $showStacktrace);
148
149            return self::ERROR;
150        }
151    }
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 The migration path is only known at run time. */
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            return $this->migrateCompiledSQL($namespace, $migration, $script);
127        } catch (Throwable $e) {
128            $this->showMessage($migration, $e, $showStacktrace);
129
130            return self::ERROR;
131        }
132    }
Executor->showEmptyMessage
153    private function showEmptyMessage(string $migration): void
154    {
155        $this->io->warn(
156            "Warning: Migration '<bright-yellow>%s</bright-yellow>' is empty. Skipped",
157            basename($migration),
158        );
159    }
Executor->showMessage
162        string $migration,
163        ?Throwable $e = null,
164        bool $showStacktrace = false,
165    ): void {
166        $io = $this->io;
167
168        if ($e) {
169            $io->error(
170                "Error: while working on migration '<bright-yellow>%s</bright-yellow>'",
171                basename($migration),
172            );
173            $io->stderr()->line('%s', $e->getMessage());
174
175            if ($showStacktrace) {
176                $io->stderr()->line('%s', $e->getTraceAsString());
177            }
178
179            return;
179            return;
182        $io->line(
183            "<bright-green>Success</bright-green>: Migration '<bright-yellow>%s</bright-yellow>' successfully applied",
184            basename($migration),
185        );
186    }
{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 The migration path is only known at run time. */
106                require $templatePath;
107            };