Completed
Push — master ( c57fe7...97cc01 )
by Cheren
07:15
created

Manager::_execute()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 38
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 27
nc 2
nop 1
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));
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...
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);
0 ignored issues
show
Bug introduced by
It seems like $adapterOptions defined by $this->_config->getEnvir...$this->_connectionName) on line 113 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...
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