|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* CakeCMS Core |
|
4
|
|
|
* |
|
5
|
|
|
* This file is part of the of the simple cms based on CakePHP 3. |
|
6
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
7
|
|
|
* file that was distributed with this source code. |
|
8
|
|
|
* |
|
9
|
|
|
* @package Core |
|
10
|
|
|
* @license MIT |
|
11
|
|
|
* @copyright MIT License http://www.opensource.org/licenses/mit-license.php |
|
12
|
|
|
* @link https://github.com/CakeCMS/Core". |
|
13
|
|
|
* @author Sergey Kalistratov <[email protected]> |
|
14
|
|
|
*/ |
|
15
|
|
|
|
|
16
|
|
|
namespace Core\Migration; |
|
17
|
|
|
|
|
18
|
|
|
use Core\Plugin; |
|
19
|
|
|
use Cake\I18n\Time; |
|
20
|
|
|
use JBZoo\Utils\Str; |
|
21
|
|
|
use Phinx\Util\Util; |
|
22
|
|
|
use JBZoo\Data\Data; |
|
23
|
|
|
use JBZoo\Utils\Arr; |
|
24
|
|
|
use Phinx\Config\Config; |
|
25
|
|
|
use Cake\Core\Configure; |
|
26
|
|
|
use Migrations\CakeAdapter; |
|
27
|
|
|
use Cake\Database\Connection; |
|
28
|
|
|
use Phinx\Db\Adapter\AdapterFactory; |
|
29
|
|
|
use Phinx\Migration\AbstractMigration; |
|
30
|
|
|
use Cake\Datasource\ConnectionManager; |
|
31
|
|
|
use Cake\Core\Exception\MissingPluginException; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Class Manager |
|
35
|
|
|
* |
|
36
|
|
|
* @package Core\Migration |
|
37
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
|
38
|
|
|
*/ |
|
39
|
|
|
class Manager |
|
40
|
|
|
{ |
|
41
|
|
|
|
|
42
|
|
|
const SCHEMA_TABLE = 'phinxlog'; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Phinx data base adapter. |
|
46
|
|
|
* |
|
47
|
|
|
* @var \Phinx\Db\Adapter\AdapterInterface |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $_adapter; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* DB adapter name. |
|
53
|
|
|
* |
|
54
|
|
|
* @var string |
|
55
|
|
|
*/ |
|
56
|
|
|
protected $_adapterName; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Phinx configuration. |
|
60
|
|
|
* |
|
61
|
|
|
* @var Config |
|
62
|
|
|
*/ |
|
63
|
|
|
protected $_config; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* Hold connection config. |
|
67
|
|
|
* |
|
68
|
|
|
* @var array|Data |
|
69
|
|
|
*/ |
|
70
|
|
|
protected $_connectionConfig = []; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* DB connection name. |
|
74
|
|
|
* |
|
75
|
|
|
* @var string |
|
76
|
|
|
*/ |
|
77
|
|
|
protected $_connectionName; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* Hold migrations. |
|
81
|
|
|
* |
|
82
|
|
|
* @var array |
|
83
|
|
|
*/ |
|
84
|
|
|
protected $_migrations = []; |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Migration plugin name. |
|
88
|
|
|
* |
|
89
|
|
|
* @var string |
|
90
|
|
|
*/ |
|
91
|
|
|
protected $_plugin; |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Manager constructor. |
|
95
|
|
|
* |
|
96
|
|
|
* @param string $plugin |
|
97
|
|
|
* @throws MissingPluginException |
|
98
|
|
|
*/ |
|
99
|
|
|
public function __construct($plugin) |
|
100
|
|
|
{ |
|
101
|
|
|
if (Plugin::loaded($plugin)) { |
|
102
|
|
|
$connectConfigure = ConnectionManager::configured(); |
|
103
|
|
|
$this->_connectionName = array_shift($connectConfigure); |
|
104
|
|
|
$this->_connectionConfig = new Data(ConnectionManager::getConfig($this->_connectionName)); |
|
|
|
|
|
|
105
|
|
|
$this->_adapterName = $this->_getAdapterName($this->_connectionConfig->get('driver')); |
|
106
|
|
|
|
|
107
|
|
|
$config = [ |
|
108
|
|
|
'paths' => [ |
|
109
|
|
|
'migrations' => Migration::getPath($plugin), |
|
110
|
|
|
], |
|
111
|
|
|
'environments' => $this->_configuration() |
|
112
|
|
|
]; |
|
113
|
|
|
|
|
114
|
|
|
$this->_plugin = $plugin; |
|
115
|
|
|
$this->_config = new Config($config); |
|
116
|
|
|
$adapterOptions = $this->_config->getEnvironment($this->_connectionName); |
|
117
|
|
|
$this->_adapter = $this->_setAdapter($adapterOptions); |
|
|
|
|
|
|
118
|
|
|
} else { |
|
119
|
|
|
throw new MissingPluginException($plugin); |
|
120
|
|
|
} |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
/** |
|
124
|
|
|
* Get plugin migrations. |
|
125
|
|
|
* |
|
126
|
|
|
* @return array |
|
127
|
|
|
*/ |
|
128
|
|
|
public function getMigrations() |
|
129
|
|
|
{ |
|
130
|
|
|
if (count($this->_migrations) <= 0) { |
|
131
|
|
|
$versions = []; |
|
132
|
|
|
$paths = $this->_config->getMigrationPaths(); |
|
133
|
|
|
foreach ($paths as $path) { |
|
134
|
|
|
$files = glob($path . '/*.php'); |
|
135
|
|
|
foreach ($files as $file) { |
|
136
|
|
|
$fileName = basename($file); |
|
137
|
|
|
if (Util::isValidMigrationFileName($fileName)) { |
|
138
|
|
|
$version = Util::getVersionFromFileName($fileName); |
|
139
|
|
|
if (Arr::key($version, $versions)) { |
|
140
|
|
|
throw new \InvalidArgumentException( |
|
141
|
|
|
__d( |
|
142
|
|
|
'core', |
|
143
|
|
|
'Duplicate migration - "%s" has the same version as "%s"', |
|
144
|
|
|
$file, |
|
145
|
|
|
$versions[$version]->getVersion() |
|
146
|
|
|
) |
|
147
|
|
|
); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
// Convert the filename to a class name. |
|
151
|
|
|
$class = Util::mapFileNameToClassName($fileName); |
|
152
|
|
|
|
|
153
|
|
|
/** @noinspection PhpIncludeInspection */ |
|
154
|
|
|
require_once $file; |
|
155
|
|
|
if (!class_exists($class)) { |
|
156
|
|
|
throw new \InvalidArgumentException( |
|
157
|
|
|
__d('core', 'Could not find class "%s" in file "%s"', $class, $file) |
|
158
|
|
|
); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
// Instantiate migration class. |
|
162
|
|
|
$migration = new $class($version); |
|
163
|
|
|
if (!($migration instanceof AbstractMigration)) { |
|
164
|
|
|
throw new \InvalidArgumentException( |
|
165
|
|
|
__d( |
|
166
|
|
|
'core', |
|
167
|
|
|
'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', |
|
168
|
|
|
$class, |
|
169
|
|
|
$file |
|
170
|
|
|
) |
|
171
|
|
|
); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
$versions[$version] = $migration; |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
ksort($versions); |
|
180
|
|
|
$this->_migrations = $versions; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
return $this->_migrations; |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
/** |
|
187
|
|
|
* Checks if the migration with version number $version as already been mark migrated |
|
188
|
|
|
* |
|
189
|
|
|
* @param int|string $version Version number of the migration to check |
|
190
|
|
|
* @return bool |
|
191
|
|
|
*/ |
|
192
|
|
|
public function isMigrated($version) |
|
193
|
|
|
{ |
|
194
|
|
|
return Arr::in($version, $this->_adapter->getVersions()); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* Migrate up action. |
|
199
|
|
|
* |
|
200
|
|
|
* @return array |
|
201
|
|
|
* @SuppressWarnings(PHPMD.UnusedLocalVariable) |
|
202
|
|
|
*/ |
|
203
|
|
|
public function migrateUp() |
|
204
|
|
|
{ |
|
205
|
|
|
$output = []; |
|
206
|
|
|
foreach ($this->getMigrations() as $version => $migration) { |
|
207
|
|
|
if ($this->isMigrated($version) === false) { |
|
208
|
|
|
$paths = $this->_config->getMigrationPaths(); |
|
209
|
|
|
foreach ($paths as $path) { |
|
210
|
|
|
$migrationFile = glob($path . DS . $version . '*'); |
|
211
|
|
|
$filePath = array_shift($migrationFile); |
|
212
|
|
|
$className = Util::mapFileNameToClassName(basename($filePath)); |
|
213
|
|
|
|
|
214
|
|
|
/** @noinspection PhpIncludeInspection */ |
|
215
|
|
|
require_once $filePath; |
|
216
|
|
|
|
|
217
|
|
|
/** @var AbstractMigration $migrate */ |
|
218
|
|
|
$migrate = new $className($version); |
|
219
|
|
|
$versions = $this->_adapter->getVersions(); |
|
220
|
|
|
|
|
221
|
|
|
// Check migrate value version. |
|
222
|
|
|
if ($migrate->getVersion() > $version) { |
|
223
|
|
|
$output[] = __d( |
|
224
|
|
|
'core', |
|
225
|
|
|
'The version «{0}» is lower than the current.', |
|
226
|
|
|
sprintf('<strong>%s</strong>', $version) |
|
227
|
|
|
); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
if (!Arr::in($version, $versions)) { |
|
231
|
|
|
$migrate->setAdapter($this->_adapter)->up(); |
|
232
|
|
|
$output[] = $this->_execute($migrate); |
|
233
|
|
|
} |
|
234
|
|
|
} |
|
235
|
|
|
} |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
return $output; |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
/** |
|
242
|
|
|
* Setup Phinx configuration. |
|
243
|
|
|
* |
|
244
|
|
|
* @return array |
|
245
|
|
|
*/ |
|
246
|
|
|
protected function _configuration() |
|
247
|
|
|
{ |
|
248
|
|
|
return [ |
|
249
|
|
|
'default_migration_table' => self::SCHEMA_TABLE, |
|
250
|
|
|
'default_database' => $this->_connectionConfig->get('name'), |
|
251
|
|
|
|
|
252
|
|
|
$this->_connectionConfig->get('name') => [ |
|
253
|
|
|
'adapter' => $this->_adapterName, |
|
254
|
|
|
'version_order' => Config::VERSION_ORDER_CREATION_TIME, |
|
255
|
|
|
'host' => $this->_connectionConfig->get('host'), |
|
256
|
|
|
'port' => $this->_connectionConfig->get('port'), |
|
257
|
|
|
'name' => $this->_connectionConfig->get('database'), |
|
258
|
|
|
'user' => $this->_connectionConfig->get('username'), |
|
259
|
|
|
'pass' => $this->_connectionConfig->get('password'), |
|
260
|
|
|
'charset' => $this->_connectionConfig->get('encoding'), |
|
261
|
|
|
'unix_socket' => $this->_connectionConfig->get('unix_socket') |
|
262
|
|
|
] |
|
263
|
|
|
]; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
/** |
|
267
|
|
|
* Execute migration. |
|
268
|
|
|
* |
|
269
|
|
|
* @param AbstractMigration $migration |
|
270
|
|
|
* @return bool|null|string |
|
271
|
|
|
*/ |
|
272
|
|
|
protected function _execute(AbstractMigration $migration) |
|
273
|
|
|
{ |
|
274
|
|
|
$time = Time::parse('now'); |
|
275
|
|
|
$version = $migration->getVersion(); |
|
276
|
|
|
$migrationName = $this->_plugin . '::' . get_class($migration); |
|
277
|
|
|
|
|
278
|
|
|
$sqlInsert = sprintf( |
|
279
|
|
|
'INSERT INTO %s (' |
|
280
|
|
|
. 'version,' |
|
281
|
|
|
. 'migration_name,' |
|
282
|
|
|
. 'start_time' |
|
283
|
|
|
. ') VALUES (' |
|
284
|
|
|
. '\'%s\',' |
|
285
|
|
|
. '\'%s\',' |
|
286
|
|
|
. '\'%s\'' |
|
287
|
|
|
. ');', |
|
288
|
|
|
self::SCHEMA_TABLE, |
|
289
|
|
|
$version, |
|
290
|
|
|
$migrationName, |
|
291
|
|
|
$time->toDateTimeString() |
|
292
|
|
|
); |
|
293
|
|
|
|
|
294
|
|
|
$this->_adapter->query($sqlInsert); |
|
295
|
|
|
|
|
296
|
|
|
$sqlCheck = sprintf( |
|
297
|
|
|
'SELECT version FROM %s WHERE version=\'%s\'', |
|
298
|
|
|
self::SCHEMA_TABLE, |
|
299
|
|
|
$version |
|
300
|
|
|
); |
|
301
|
|
|
|
|
302
|
|
|
$versionResult = $this->_adapter->fetchRow($sqlCheck); |
|
303
|
|
|
if (count((array) $versionResult) > 0) { |
|
304
|
|
|
return __d( |
|
305
|
|
|
'core', |
|
306
|
|
|
'The version «{0}» of plugin «{1}» has bin success migrated.', |
|
307
|
|
|
sprintf('<strong>%s</strong>', $version), |
|
308
|
|
|
sprintf('<strong>%s</strong>', __d(Str::low($this->_plugin), $this->_plugin)) |
|
309
|
|
|
); |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
return false; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
/** |
|
316
|
|
|
* Get adapter name from application driver. |
|
317
|
|
|
* |
|
318
|
|
|
* @param string $driver |
|
319
|
|
|
* @return string |
|
320
|
|
|
*/ |
|
321
|
|
|
protected function _getAdapterName($driver) |
|
322
|
|
|
{ |
|
323
|
|
|
switch ($driver) { |
|
324
|
|
|
case 'Cake\Database\Driver\Mysql': |
|
325
|
|
|
return 'mysql'; |
|
326
|
|
|
|
|
327
|
|
|
case 'Cake\Database\Driver\Postgres': |
|
328
|
|
|
return 'pgsql'; |
|
329
|
|
|
|
|
330
|
|
|
case 'Cake\Database\Driver\Sqlite': |
|
331
|
|
|
return 'sqlite'; |
|
332
|
|
|
|
|
333
|
|
|
case 'Cake\Database\Driver\Sqlserver': |
|
334
|
|
|
return 'sqlsrv'; |
|
335
|
|
|
} |
|
336
|
|
|
|
|
337
|
|
|
throw new \InvalidArgumentException(__d('core', 'Could not infer database type from driver')); |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
/** |
|
341
|
|
|
* Get database adapter. |
|
342
|
|
|
* |
|
343
|
|
|
* @param array $options |
|
344
|
|
|
* @return \Phinx\Db\Adapter\AdapterInterface |
|
345
|
|
|
*/ |
|
346
|
|
|
protected function _setAdapter(array $options) |
|
347
|
|
|
{ |
|
348
|
|
|
$adapterFactory = AdapterFactory::instance(); |
|
349
|
|
|
$connection = new Connection($this->_connectionConfig->getArrayCopy()); |
|
350
|
|
|
$adapter = $adapterFactory->getAdapter($this->_adapterName, $options); |
|
351
|
|
|
|
|
352
|
|
|
return new CakeAdapter($adapter, $connection); |
|
353
|
|
|
} |
|
354
|
|
|
} |
|
355
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.