Completed
Push — master ( a49283...95f5a3 )
by Nekrasov
03:40
created

Migrator::getAllMigrations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Arrilot\BitrixMigrations;
4
5
use Arrilot\BitrixIblockHelper\HLBlock;
6
use Arrilot\BitrixIblockHelper\IblockId;
7
use Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface;
8
use Arrilot\BitrixMigrations\Interfaces\FileStorageInterface;
9
use Arrilot\BitrixMigrations\Interfaces\MigrationInterface;
10
use Arrilot\BitrixMigrations\Storages\BitrixDatabaseStorage;
11
use Arrilot\BitrixMigrations\Storages\FileStorage;
12
use Exception;
13
14
class Migrator
15
{
16
    /**
17
     * Migrator configuration array.
18
     *
19
     * @var array
20
     */
21
    protected $config;
22
23
    /**
24
     * Directory to store m.
25
     *
26
     * @var string
27
     */
28
    protected $dir;
29
30
    /**
31
     * Directory to store archive m.
32
     *
33
     * @var string
34
     */
35
    protected $dir_archive;
36
37
    /**
38
     * Files interactions.
39
     *
40
     * @var FileStorageInterface
41
     */
42
    protected $files;
43
44
    /**
45
     * Interface that gives us access to the database.
46
     *
47
     * @var DatabaseStorageInterface
48
     */
49
    protected $database;
50
51
    /**
52
     * TemplatesCollection instance.
53
     *
54
     * @var TemplatesCollection
55
     */
56
    protected $templates;
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param array                    $config
62
     * @param TemplatesCollection      $templates
63
     * @param DatabaseStorageInterface $database
64
     * @param FileStorageInterface     $files
65
     */
66
    public function __construct($config, TemplatesCollection $templates, DatabaseStorageInterface $database = null, FileStorageInterface $files = null)
67
    {
68
        $this->config = $config;
69
        $this->dir = $config['dir'];
70
        $this->dir_archive = isset($config['dir_archive']) ? $config['dir_archive'] : 'archive';
71
72
        $this->templates = $templates;
73
        $this->database = $database ?: new BitrixDatabaseStorage($config['table']);
74
        $this->files = $files ?: new FileStorage();
75
    }
76
77
    /**
78
     * Create migration file.
79
     *
80
     * @param string $name         - migration name
81
     * @param string $templateName
82
     * @param array  $replace      - array of placeholders that should be replaced with a given values.
83
     * @param string  $subDir
84
     *
85
     * @return string
86
     */
87
    public function createMigration($name, $templateName, array $replace = [], $subDir = '')
88
    {
89
        $targetDir = $this->dir;
90
        $subDir = trim(str_replace('\\', '/', $subDir), '/');
91
        if ($subDir) {
92
            $targetDir .= '/' . $subDir;
93
        }
94
95
        $this->files->createDirIfItDoesNotExist($targetDir);
96
97
        $fileName = $this->constructFileName($name);
98
        $className = $this->getMigrationClassNameByFileName($fileName);
99
        $templateName = $this->templates->selectTemplate($templateName);
100
101
        $template = $this->files->getContent($this->templates->getTemplatePath($templateName));
102
        $template = $this->replacePlaceholdersInTemplate($template, array_merge($replace, ['className' => $className]));
103
104
        $this->files->putContent($targetDir.'/'.$fileName.'.php', $template);
105
106
        return $fileName;
107
    }
108
109
    /**
110
     * Run all migrations that were not run before.
111
     */
112
    public function runMigrations()
113
    {
114
        $migrations = $this->getMigrationsToRun();
115
        $ran = [];
116
117
        if (empty($migrations)) {
118
            return $ran;
119
        }
120
121
        foreach ($migrations as $migration) {
122
            $this->runMigration($migration);
123
            $ran[] = $migration;
124
        }
125
126
        return $ran;
127
    }
128
129
    /**
130
     * Run a given migration.
131
     *
132
     * @param string $file
133
     *
134
     * @throws Exception
135
     *
136
     * @return string
137
     */
138
    public function runMigration($file)
139
    {
140
        $migration = $this->getMigrationObjectByFileName($file);
141
142
        $this->disableBitrixIblockHelperCache();
143
144
        if ($migration->up() === false) {
145
            throw new Exception("Migration up from {$file}.php returned false");
146
        }
147
148
        $this->logSuccessfulMigration($file);
149
    }
150
151
    /**
152
     * Log successful migration.
153
     *
154
     * @param string $migration
155
     *
156
     * @return void
157
     */
158
    public function logSuccessfulMigration($migration)
159
    {
160
        $this->database->logSuccessfulMigration($migration);
161
    }
162
163
    /**
164
     * Get ran migrations.
165
     *
166
     * @return array
167
     */
168
    public function getRanMigrations()
169
    {
170
        return $this->database->getRanMigrations();
171
    }
172
173
    /**
174
     * Get all migrations.
175
     *
176
     * @return array
177
     */
178
    public function getAllMigrations()
179
    {
180
        return $this->files->getMigrationFiles($this->dir);
181
    }
182
183
    /**
184
     * Determine whether migration file for migration exists.
185
     *
186
     * @param string $migration
187
     *
188
     * @return bool
189
     */
190
    public function doesMigrationFileExist($migration)
191
    {
192
        return $this->files->exists($this->getMigrationFilePath($migration));
193
    }
194
195
    /**
196
     * Rollback a given migration.
197
     *
198
     * @param string $file
199
     *
200
     * @throws Exception
201
     *
202
     * @return mixed
203
     */
204
    public function rollbackMigration($file)
205
    {
206
        $migration = $this->getMigrationObjectByFileName($file);
207
208
        if ($migration->down() === false) {
209
            throw new Exception("<error>Can't rollback migration:</error> {$file}.php");
210
        }
211
212
        $this->removeSuccessfulMigrationFromLog($file);
213
    }
214
215
    /**
216
     * Remove a migration name from the database so it can be run again.
217
     *
218
     * @param string $file
219
     *
220
     * @return void
221
     */
222
    public function removeSuccessfulMigrationFromLog($file)
223
    {
224
        $this->database->removeSuccessfulMigrationFromLog($file);
225
    }
226
227
    /**
228
     * Delete migration file.
229
     *
230
     * @param string $migration
231
     *
232
     * @return bool
233
     */
234
    public function deleteMigrationFile($migration)
235
    {
236
        return $this->files->delete($this->getMigrationFilePath($migration));
237
    }
238
239
    /**
240
     * Get array of migrations that should be ran.
241
     *
242
     * @return array
243
     */
244
    public function getMigrationsToRun()
245
    {
246
        $allMigrations = $this->getAllMigrations();
247
248
        $ranMigrations = $this->getRanMigrations();
249
250
        return array_diff($allMigrations, $ranMigrations);
251
    }
252
253
    /**
254
     * Move migration files.
255
     *
256
     * @param array $files
257
     * @param string $toDir
258
     *
259
     * @return int
260
     */
261
    public function moveMigrationFiles($files = [], $toDir = '')
262
    {
263
        $toDir = trim($toDir ?: $this->dir_archive, '/');
264
        $files = $files ?: $this->getAllMigrations();
265
        $this->files->createDirIfItDoesNotExist("$this->dir/$toDir");
266
267
        $count = 0;
268
        foreach ($files as $migration) {
269
            $from = $this->getMigrationFilePath($migration);
270
            $to = "$this->dir/$toDir/$migration.php";
271
272
            if ($from == $to) {
273
                continue;
274
            }
275
276
            $flag = $this->files->move($from, $to);
277
278
            if ($flag) {
279
                $count++;
280
            }
281
        }
282
283
        return $count;
284
    }
285
286
    /**
287
     * Construct migration file name from migration name and current time.
288
     *
289
     * @param string $name
290
     *
291
     * @return string
292
     */
293
    protected function constructFileName($name)
294
    {
295
        list($usec, $sec) = explode(' ', microtime());
296
297
        $usec = substr($usec, 2, 6);
298
299
        return date('Y_m_d_His', $sec).'_'.$usec.'_'.$name;
300
    }
301
302
    /**
303
     * Get a migration class name by a migration file name.
304
     *
305
     * @param string $file
306
     *
307
     * @return string
308
     */
309
    protected function getMigrationClassNameByFileName($file)
310
    {
311
        $fileExploded = explode('_', $file);
312
313
        $datePart = implode('_', array_slice($fileExploded, 0, 5));
314
        $namePart = implode('_', array_slice($fileExploded, 5));
315
316
        return Helpers::studly($namePart.'_'.$datePart);
317
    }
318
319
    /**
320
     * Replace all placeholders in the stub.
321
     *
322
     * @param string $template
323
     * @param array  $replace
324
     *
325
     * @return string
326
     */
327
    protected function replacePlaceholdersInTemplate($template, array $replace)
328
    {
329
        foreach ($replace as $placeholder => $value) {
330
            $template = str_replace("__{$placeholder}__", $value, $template);
331
        }
332
333
        return $template;
334
    }
335
336
    /**
337
     * Resolve a migration instance from a file.
338
     *
339
     * @param string $file
340
     *
341
     * @throws Exception
342
     *
343
     * @return MigrationInterface
344
     */
345
    protected function getMigrationObjectByFileName($file)
346
    {
347
        $class = $this->getMigrationClassNameByFileName($file);
348
349
        $this->requireMigrationFile($file);
350
351
        $object = new $class();
352
353
        if (!$object instanceof MigrationInterface) {
354
            throw new Exception("Migration class {$class} must implement Arrilot\\BitrixMigrations\\Interfaces\\MigrationInterface");
355
        }
356
357
        return $object;
358
    }
359
360
    /**
361
     * Require migration file.
362
     *
363
     * @param string $file
364
     *
365
     * @return void
366
     */
367
    protected function requireMigrationFile($file)
368
    {
369
        $this->files->requireFile($this->getMigrationFilePath($file));
370
    }
371
372
    /**
373
     * Get path to a migration file.
374
     *
375
     * @param string $migration
376
     *
377
     * @return string
378
     */
379
    protected function getMigrationFilePath($migration)
380
    {
381
        $files = Helpers::rGlob("$this->dir/$migration.php");
382
        if (count($files) != 1) {
383
            throw new \Exception("Not found migration file");
384
        }
385
386
        return $files[0];
387
    }
388
389
    /**
390
     * If package arrilot/bitrix-iblock-helper is loaded then we should disable its caching to avoid problems.
391
     */
392
    private function disableBitrixIblockHelperCache()
393
    {
394
        if (class_exists('\\Arrilot\\BitrixIblockHelper\\IblockId')) {
395
            IblockId::setCacheTime(0);
396
            if (method_exists('\\Arrilot\\BitrixIblockHelper\\IblockId', 'flushLocalCache')) {
397
                IblockId::flushLocalCache();
398
            }
399
        }
400
401
        if (class_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock')) {
402
            HLBlock::setCacheTime(0);
403
            if (method_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock', 'flushLocalCache')) {
404
                HLBlock::flushLocalCache();
405
            }
406
        }
407
    }
408
}
409