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