Completed
Push — master ( 9bd1b4...a21c41 )
by Joao
04:25 queued 02:23
created

Migration::getDatabaseClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace ByJG\DbMigration;
4
5
use ByJG\AnyDataset\DbDriverInterface;
6
use ByJG\AnyDataset\Factory;
7
use ByJG\DbMigration\Database\DatabaseInterface;
8
use ByJG\DbMigration\Exception\DatabaseIsIncompleteException;
9
use ByJG\Util\Uri;
10
11
class Migration
12
{
13
    /**
14
     * @var Uri
15
     */
16
    protected $uri;
17
18
    /**
19
     * @var string
20
     */
21
    protected $_folder;
22
23
    /**
24
     * @var DbDriverInterface
25
     */
26
    protected $dbDriver;
27
28
    /**
29
     * @var DatabaseInterface
30
     */
31
    protected $_dbCommand;
32
33
    /**
34
     * @var Callable
35
     */
36
    protected $_callableProgress;
37
    
38
    /**
39
     * Migration constructor.
40
     *
41
     * @param Uri $uri
42
     * @param string $_folder
43
     */
44
    public function __construct(Uri $uri, $_folder)
45
    {
46
        $this->uri = $uri;
47
        $this->_folder = $_folder;
48
49
        if (!file_exists($this->_folder . '/base.sql')) {
50
            throw new \InvalidArgumentException("Migration script '{$this->_folder}/base.sql' not found");
51
        }
52
    }
53
54
    /**
55
     * @return DbDriverInterface
56
     */
57
    public function getDbDriver()
58
    {
59
        if (is_null($this->dbDriver)) {
60
            $this->dbDriver = Factory::getDbRelationalInstance($this->uri->__toString());
61
        }
62
        return $this->dbDriver;
63
    }
64
65
    /**
66
     * @return DatabaseInterface
67
     */
68
    public function getDbCommand()
69
    {
70
        if (is_null($this->_dbCommand)) {
71
            $class = $this->getDatabaseClassName();
72
            $this->_dbCommand = new $class($this->getDbDriver());
73
        }
74
        return $this->_dbCommand;
75
    }
76
77
    protected function getDatabaseClassName()
78
    {
79
        return "\\ByJG\\DbMigration\\Database\\" . ucfirst($this->uri->getScheme()) . "Database";
80
    }
81
82
    /**
83
     * Get the full path and name of the "base.sql" script
84
     *
85
     * @return string
86
     */
87
    public function getBaseSql()
88
    {
89
        return $this->_folder . "/base.sql";
90
    }
91
92
    /**
93
     * Get the full path script based on the version
94
     *
95
     * @param $version
96
     * @param $increment
97
     * @return string
98
     */
99
    public function getMigrationSql($version, $increment)
100
    {
101
        // I could use the GLOB_BRACE but it is not supported on ALPINE distros.
102
        // So, I have to call multiple times to simulate the braces.
103
104
        if (intval($version) != $version) {
105
            throw new \InvalidArgumentException("Version '$version' should be a integer number");
106
        }
107
        $version = intval($version);
108
109
        $filePattern = $this->_folder
110
            . "/migrations"
111
            . "/" . ($increment < 0 ? "down" : "up")
112
            . "/*%s.sql";
113
114
        $result = array_merge(
115
            glob(sprintf($filePattern, "$version")),
116
            glob(sprintf($filePattern, "$version-dev"))
117
        );
118
119
        // Valid values are 0 or 1
120
        if (count($result) > 1) {
121
            throw new \InvalidArgumentException("You have two files with the same version $version number");
122
        }
123
124
        foreach ($result as $file) {
125
            if (intval(basename($file)) === $version) {
126
                return $file;
127
            }
128
        }
129
        return null;
130
    }
131
132
    /**
133
     * Create the database it it does not exists. Does not use this methos in a production environment; 
134
     */
135
    public function prepareEnvironment()
136
    {
137
        $class = $this->getDatabaseClassName();
138
        $class::prepareEnvironment($this->uri);
139
    }
140
    
141
    /**
142
     * Restore the database using the "base.sql" script and run all migration scripts
143
     * Note: the database must exists. If dont exist run the method prepareEnvironment.  
144
     *
145
     * @param int $upVersion
146
     */
147
    public function reset($upVersion = null)
148
    {
149
        if ($this->_callableProgress) {
150
            call_user_func_array($this->_callableProgress, ['reset', 0]);
151
        }
152
        $this->getDbCommand()->dropDatabase();
153
        $this->getDbCommand()->createDatabase();
154
        $this->getDbCommand()->createVersion();
155
        $this->getDbCommand()->executeSql(file_get_contents($this->getBaseSql()));
156
        $this->getDbCommand()->setVersion(0, 'complete');
157
        $this->up($upVersion);
158
    }
159
160
    public function createVersion()
161
    {
162
        $this->getDbCommand()->createVersion();
163
    }
164
165
    public function updateTableVersion()
166
    {
167
        $this->getDbCommand()->updateVersionTable();
168
    }
169
170
    /**
171
     * Get the current database version
172
     *
173
     * @return int
174
     */
175
    public function getCurrentVersion()
176
    {
177
        return $this->getDbCommand()->getVersion();
178
    }
179
180
    /**
181
     * @param $currentVersion
182
     * @param $upVersion
183
     * @param $increment
184
     * @return bool
185
     */
186
    protected function canContinue($currentVersion, $upVersion, $increment)
187
    {
188
        $existsUpVersion = ($upVersion !== null);
189
        $compareVersion =
190
            intval($currentVersion) < intval($upVersion)
191
                ? -1
192
                : (
193
                    intval($currentVersion) > intval($upVersion)
194
                        ? 1
195
                        : 0
196
                );
197
198
        return !($existsUpVersion && ($compareVersion === intval($increment)));
199
    }
200
201
    /**
202
     * Method for execute the migration.
203
     *
204
     * @param int $upVersion
205
     * @param int $increment Can accept 1 for UP or -1 for down
206
     * @param bool $force
207
     * @throws \ByJG\DbMigration\Exception\DatabaseIsIncompleteException
208
     */
209
    protected function migrate($upVersion, $increment, $force)
210
    {
211
        $versionInfo = $this->getCurrentVersion();
212
        $currentVersion = intval($versionInfo['version']) + $increment;
213
214
        if (strpos($versionInfo['status'], 'partial') !== false && !$force) {
215
            throw new DatabaseIsIncompleteException('Database was not fully updated. Use --force for migrate.');
216
        }
217
218
        while ($this->canContinue($currentVersion, $upVersion, $increment)
219
            && file_exists($file = $this->getMigrationSql($currentVersion, $increment))
220
        ) {
221
            if ($this->_callableProgress) {
222
                call_user_func_array($this->_callableProgress, ['migrate', $currentVersion]);
223
            }
224
225
            $this->getDbCommand()->setVersion($currentVersion, 'partial ' . ($increment>0 ? 'up' : 'down'));
226
            $this->getDbCommand()->executeSql(file_get_contents($file));
227
            $this->getDbCommand()->setVersion($currentVersion, 'complete');
228
            $currentVersion = $currentVersion + $increment;
229
        }
230
    }
231
232
    /**
233
     * Run all scripts to up the database version from current up to latest version or the specified version.
234
     *
235
     * @param int $upVersion
236
     * @param bool $force
237
     */
238
    public function up($upVersion = null, $force = false)
239
    {
240
        $this->migrate($upVersion, 1, $force);
241
    }
242
243
    /**
244
     * Run all scripts to up or down the database version from current up to latest version or the specified version.
245
     *
246
     * @param int $upVersion
247
     * @param bool $force
248
     */
249
    public function update($upVersion = null, $force = false)
250
    {
251
        $versionInfo = $this->getCurrentVersion();
252
        $version = intval($versionInfo['version']);
253
        $increment = 1;
254
        if ($upVersion !== null && $upVersion < $version) {
255
            $increment = -1;
256
        }
257
        $this->migrate($upVersion, $increment, $force);
258
    }
259
260
    /**
261
     * Run all scripts to down the database version from current version up to the specified version.
262
     *
263
     * @param int $upVersion
264
     * @param bool $force
265
     */
266
    public function down($upVersion, $force = false)
267
    {
268
        $this->migrate($upVersion, -1, $force);
269
    }
270
    
271
    public function addCallbackProgress(Callable $callable)
272
    {
273
        $this->_callableProgress = $callable;
274
    }
275
}
276