Passed
Push — master ( bc6df6...b7a806 )
by y
05:58
created

Migrator::getDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Helix\DB;
4
5
use Helix\DB;
6
7
/**
8
 * Migrates.
9
 *
10
 * @method static static factory(DB $db, string $dir);
11
 *
12
 * @see MigrationInterface
13
 */
14
class Migrator {
15
16
    use FactoryTrait;
17
18
    /**
19
     * @var DB
20
     */
21
    protected $db;
22
23
    /**
24
     * @var string
25
     */
26
    protected $dir;
27
28
    /**
29
     * @var Table
30
     */
31
    protected $table;
32
33
    /**
34
     * @param DB $db
35
     * @param string $dir
36
     */
37
    public function __construct (DB $db, string $dir) {
38
        $this->db = $db;
39
        $this->dir = $dir;
40
        $this->table ??= $db['__migrations__'] ?? $db->getSchema()->createTable('__migrations__', [
41
                'sequence' => Schema::T_STRING_STRICT | Schema::I_PRIMARY
42
            ])['__migrations__'];
43
    }
44
45
    /**
46
     * Migrates down within a transaction.
47
     *
48
     * @param string $to Migration sequence identifier, or `null` to step down once.
49
     * @return null|string The resulting current sequence identifier.
50
     */
51
    public function down (string $to = null): ?string {
52
        return $this->db->transact(function() use ($to) {
53
            $current = $this->getCurrent();
54
            // walk newest to oldest
55
            foreach (array_reverse($this->glob(), true) as $sequence => $file) {
56
                if ($current and $to === $current) {
57
                    break;
58
                }
59
                if ($current < $sequence) {
60
                    continue;
61
                }
62
                $this->db->transact(fn() => $this->getMigration($file)->down($this->db->getSchema()));
63
                $this->table->delete(['sequence' => $sequence]);
64
                $current = $this->getCurrent();
65
                if ($to === null) {
66
                    break;
67
                }
68
            }
69
            return $current;
70
        });
71
    }
72
73
    /**
74
     * Returns the sequence identifier of the most recent upgrade.
75
     *
76
     * @return null|string
77
     */
78
    public function getCurrent (): ?string {
79
        return $this->table->select([$this->table['sequence']->max()])->getResult();
80
    }
81
82
    /**
83
     * @return string
84
     */
85
    final public function getDir (): string {
86
        return $this->dir;
87
    }
88
89
    /**
90
     * @param array $spec
91
     * @return MigrationInterface
92
     */
93
    protected function getMigration (string $file) {
94
        $migration = include "{$file}";
95
        assert($migration instanceof MigrationInterface);
96
        return $migration;
97
    }
98
99
    /**
100
     * Scans the migration directory for `<SEQUENCE>.php` files.
101
     *
102
     * @return string[] [ sequence => file ]
103
     */
104
    protected function glob () {
105
        $files = [];
106
        foreach (glob("{$this->dir}/*.php") as $file) {
107
            $files[basename($file, '.php')] = $file;
108
        }
109
        return $files;
110
    }
111
112
    /**
113
     * Migrates up within a transaction.
114
     *
115
     * @param null|string $to Migration sequence identifier, or `null` for all upgrades.
116
     * @return null|string The resulting current sequence identifier.
117
     */
118
    public function up (string $to = null): ?string {
119
        return $this->db->transact(function() use ($to) {
120
            $current = $this->getCurrent();
121
            // walk oldest to newest
122
            foreach ($this->glob() as $sequence => $file) {
123
                if ($current and $to === $current) {
124
                    break;
125
                }
126
                if ($current >= $sequence) {
127
                    continue;
128
                }
129
                $this->db->transact(fn() => $this->getMigration($file)->up($this->db->getSchema()));
130
                $this->table->insert(['sequence' => $sequence]);
131
                $current = $sequence;
132
            }
133
            return $current;
134
        });
135
    }
136
}