• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / database-extra / 16536129688

23 Aug 2024 09:59PM UTC coverage: 100.0%. Remained the same
16536129688

push

github

natanfelles
Upgrade coding standard

150 of 150 relevant lines covered (100.0%)

3.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/Migrator.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework Database Extra Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\Database\Extra;
11

12
use Framework\Database\Database;
13
use Framework\Database\Definition\Table\TableDefinition;
14
use Framework\Helpers\Isolation;
15
use Generator;
16
use InvalidArgumentException;
17

18
/**
19
 * Class Migrator.
20
 *
21
 * @package database-extra
22
 */
23
class Migrator
24
{
25
    protected Database $database;
26
    protected string $table;
27
    /**
28
     * @var array<int,string>
29
     */
30
    protected array $directories = [];
31

32
    /**
33
     * @param Database $database
34
     * @param array<string> $directories
35
     * @param string $table
36
     */
37
    public function __construct(
38
        Database $database,
39
        array $directories,
40
        string $table = 'Migrations'
41
    ) {
42
        foreach ($directories as $directory) {
9✔
43
            $this->addDirectory($directory);
9✔
44
        }
45
        $this->setDatabase($database)
9✔
46
            ->setTable($table)
9✔
47
            ->prepare();
9✔
48
    }
49

50
    public function setDatabase(Database $database) : static
51
    {
52
        $this->database = $database;
9✔
53
        return $this;
9✔
54
    }
55

56
    public function getDatabase() : Database
57
    {
58
        return $this->database;
9✔
59
    }
60

61
    public function addDirectory(string $directory) : static
62
    {
63
        $realpath = \realpath($directory);
9✔
64
        if ($realpath === false || !\is_dir($realpath)) {
9✔
65
            throw new InvalidArgumentException('Directory path is invalid: ' . $directory);
1✔
66
        }
67
        $this->directories[] = $realpath . \DIRECTORY_SEPARATOR;
9✔
68
        return $this;
9✔
69
    }
70

71
    /**
72
     * @return array<int,string>
73
     */
74
    public function getDirectories() : array
75
    {
76
        return $this->directories;
5✔
77
    }
78

79
    public function setTable(string $table) : static
80
    {
81
        $this->table = $table;
9✔
82
        return $this;
9✔
83
    }
84

85
    public function getTable() : string
86
    {
87
        return $this->table;
9✔
88
    }
89

90
    protected function prepare() : void
91
    {
92
        $result = $this->getDatabase()->query(
9✔
93
            'SHOW TABLES LIKE ' . $this->getDatabase()->quote($this->getTable())
9✔
94
        )->fetch();
9✔
95
        if ($result) {
9✔
96
            return;
1✔
97
        }
98
        $this->getDatabase()->createTable()
8✔
99
            ->table($this->getTable())
8✔
100
            ->definition(static function (TableDefinition $definition) : void {
8✔
101
                $definition->column('id')->int()->autoIncrement()->primaryKey();
8✔
102
                $definition->column('migration')->varchar(255);
8✔
103
                $definition->column('timestamp')->timestamp()->notNull();
8✔
104
                $definition->index()->key('migration');
8✔
105
            })->run();
8✔
106
    }
107

108
    /**
109
     * Get current migrated version from Database.
110
     *
111
     * @return string|null
112
     */
113
    public function getLastMigrationName() : ?string
114
    {
115
        return $this->database->select()
4✔
116
            ->from($this->getTable())
4✔
117
            ->orderByDesc('id')
4✔
118
            ->limit(1)
4✔
119
            ->run()
4✔
120
            ->fetch()->migration ?? null;
4✔
121
    }
122

123
    /**
124
     * @return array<int,string>
125
     */
126
    protected function getFiles() : array
127
    {
128
        $files = [];
4✔
129
        foreach ($this->getDirectories() as $directory) {
4✔
130
            foreach ((array) \glob($directory . '*.php') as $filename) {
4✔
131
                if ($filename && \is_file($filename)) {
4✔
132
                    $files[] = [
4✔
133
                        'basename' => \basename($filename, '.php'),
4✔
134
                        'filename' => $filename,
4✔
135
                    ];
4✔
136
                }
137
            }
138
        }
139
        \usort($files, static function ($file1, $file2) {
4✔
140
            return \strnatcmp($file1['basename'], $file2['basename']);
4✔
141
        });
4✔
142
        $result = [];
4✔
143
        foreach ($files as $file) {
4✔
144
            $result[] = $file['filename'];
4✔
145
        }
146
        return $result;
4✔
147
    }
148

149
    /**
150
     * @return Generator<string,Migration>
151
     */
152
    protected function getMigrationsAsc() : Generator
153
    {
154
        yield from $this->getMigrations($this->getFiles());
3✔
155
    }
156

157
    /**
158
     * @return Generator<string,Migration>
159
     */
160
    protected function getMigrationsDesc() : Generator
161
    {
162
        yield from $this->getMigrations(\array_reverse($this->getFiles()));
3✔
163
    }
164

165
    /**
166
     * @param array<string> $files
167
     *
168
     * @return Generator<string,Migration>
169
     */
170
    protected function getMigrations(array $files) : Generator
171
    {
172
        foreach ($files as $file) {
4✔
173
            $migration = Isolation::require($file);
4✔
174
            if ($migration instanceof Migration) {
4✔
175
                $migration->setDatabase($this->getDatabase());
4✔
176
                $file = \basename($file, '.php');
4✔
177
                yield $file => $migration;
4✔
178
            }
179
        }
180
    }
181

182
    /**
183
     * @param int|null $quantity
184
     *
185
     * @return Generator<string>
186
     */
187
    public function migrateDown(?int $quantity = null) : Generator
188
    {
189
        $count = 0;
2✔
190
        $last = $this->getLastMigrationName() ?? '';
2✔
191
        foreach ($this->getMigrationsDesc() as $name => $migration) {
2✔
192
            $cmp = \strnatcmp($last, $name);
2✔
193
            if ($cmp < 0) {
2✔
194
                continue;
2✔
195
            }
196
            $migration->down();
2✔
197
            $this->deleteRow($name);
2✔
198
            yield $name;
2✔
199
            $count++;
2✔
200
            if ($count === $quantity) {
2✔
201
                break;
1✔
202
            }
203
        }
204
    }
205

206
    /**
207
     * @param int|null $quantity
208
     *
209
     * @return Generator<string>
210
     */
211
    public function migrateUp(?int $quantity = null) : Generator
212
    {
213
        $count = 0;
2✔
214
        $last = $this->getLastMigrationName() ?? '';
2✔
215
        foreach ($this->getMigrationsAsc() as $name => $migration) {
2✔
216
            $cmp = \strnatcmp($last, $name);
2✔
217
            if ($cmp >= 0) {
2✔
218
                continue;
2✔
219
            }
220
            $migration->up();
2✔
221
            $this->insertRow($name);
2✔
222
            yield $name;
2✔
223
            $count++;
2✔
224
            if ($count === $quantity) {
2✔
225
                break;
1✔
226
            }
227
        }
228
    }
229

230
    protected function insertRow(string $name) : int | string
231
    {
232
        return $this->getDatabase()->insert()
3✔
233
            ->into($this->getTable())
3✔
234
            ->set([
3✔
235
                'migration' => $name,
3✔
236
                'timestamp' => \gmdate('Y-m-d H:i:s'),
3✔
237
            ])->run();
3✔
238
    }
239

240
    protected function deleteRow(string $name) : int | string
241
    {
242
        return $this->getDatabase()->delete()
3✔
243
            ->from($this->getTable())
3✔
244
            ->whereEqual('migration', $name)
3✔
245
            ->orderByDesc('id')
3✔
246
            ->limit(1)
3✔
247
            ->run();
3✔
248
    }
249

250
    /**
251
     * @param string $name
252
     *
253
     * @return Generator<string>
254
     */
255
    public function migrateTo(string $name) : Generator
256
    {
257
        $last = $this->getLastMigrationName() ?? '';
1✔
258
        $cmp = \strnatcmp($last, $name);
1✔
259
        if ($cmp === 0) {
1✔
260
            return;
1✔
261
        }
262
        if ($cmp < 0) {
1✔
263
            foreach ($this->getMigrationsAsc() as $n => $migration) {
1✔
264
                if (\strnatcmp($n, $name) > 0) {
1✔
265
                    continue;
1✔
266
                }
267
                if (\strnatcmp($last, $n) >= 0) {
1✔
268
                    continue;
1✔
269
                }
270
                $migration->up();
1✔
271
                $this->insertRow($n);
1✔
272
                yield $n;
1✔
273
            }
274
            return;
1✔
275
        }
276
        foreach ($this->getMigrationsDesc() as $n => $migration) {
1✔
277
            if (\strnatcmp($name, $n) > 0) {
1✔
278
                continue;
1✔
279
            }
280
            if (\strnatcmp($last, $n) < 0) {
1✔
281
                continue;
1✔
282
            }
283
            $migration->down();
1✔
284
            $this->deleteRow($n);
1✔
285
            yield $n;
1✔
286
        }
287
    }
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc