Completed
Push — master ( 4e8255...5cb298 )
by Nekrasov
06:39
created

Migrator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 7
dl 0
loc 332
rs 10
c 0
b 0
f 0

18 Methods

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