Completed
Push — master ( fd976a...f5777b )
by Cheren
01:36
created

Manager::migrateUp()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.439
c 0
b 0
f 0
cc 6
eloc 21
nc 7
nop 0
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));
0 ignored issues
show
Bug introduced by
It seems like \Cake\Datasource\Connect...$this->_connectionName) targeting Cake\Core\StaticConfigTrait::getConfig() can also be of type null; however, JBZoo\Data\Data::__construct() does only seem to accept array|string, maybe add an additional type check?

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $adapterOptions defined by $this->_config->getEnvir...$this->_connectionName) on line 116 can also be of type null; however, Core\Migration\Manager::_setAdapter() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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