Completed
Push — master ( ad0611...abd676 )
by Tom
09:08 queued 04:30
created

getMysqlClientToolConnectionString()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 21
rs 9.0534
cc 4
eloc 15
nc 8
nop 0
1
<?php
2
3
namespace N98\Util\Console\Helper;
4
5
use PDO;
6
use PDOException;
7
use RuntimeException;
8
use Symfony\Component\Console\Helper\Helper as AbstractHelper;
9
use N98\Magento\Application;
10
use Symfony\Component\Console\Output\NullOutput;
11
use Symfony\Component\Console\Output\OutputInterface;
12
13
class DatabaseHelper extends AbstractHelper
14
{
15
    /**
16
     * @var array
17
     */
18
    protected $dbSettings = null;
19
20
    /**
21
     * @var bool
22
     */
23
    protected $isSocketConnect = false;
24
25
    /**
26
     * @var PDO
27
     */
28
    protected $_connection = null;
29
30
    /**
31
     * @var array
32
     */
33
    protected $_tables;
34
35
    /**
36
     * @param OutputInterface $output
37
     *
38
     * @throws RuntimeException
39
     * @return void
40
     */
41
    public function detectDbSettings(OutputInterface $output)
42
    {
43
        if ($this->dbSettings !== null) {
44
            return;
45
        }
46
47
        $magentoHelper = $this->getHelperSet()->getCommand()->getHelper('magento');
48
        $config = $magentoHelper->getBaseConfig(); // @TODO Use \Magento\Framework\App\DeploymentConfig ?
49
50
        if (!isset($config['db'])) {
51
            $output->writeln('<error>DB settings was not found in config.xml file</error>');
52
            return;
53
        }
54
55
        if (!isset($config['db']['connection']['default'])) {
56
            throw new RuntimeException('Cannot find default connection config in app/etc/config.php');
57
        }
58
59
        $this->dbSettings = (array) $config['db']['connection']['default'];
60
61
        $this->dbSettings['prefix'] = '';
62
        if (isset($config['db']['table_prefix'])) {
63
            $this->dbSettings['prefix'] = (string) $config['db']['table_prefix'];
64
        }
65
66 View Code Duplication
        if (strpos($this->dbSettings['host'], ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
            list($this->dbSettings['host'], $this->dbSettings['port']) = explode(':', $this->dbSettings['host']);
68
        }
69
70
        if (isset($this->dbSettings['comment'])) {
71
            unset($this->dbSettings['comment']);
72
        }
73
74
        if (isset($this->dbSettings['unix_socket'])) {
75
            $this->isSocketConnect = true;
76
        }
77
    }
78
79
    /**
80
     * Connects to the database without initializing magento
81
     *
82
     * @param OutputInterface $output = null
0 ignored issues
show
Documentation introduced by
Should the type for parameter $output not be null|OutputInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
83
     *
84
     * @return PDO
85
     * @throws RuntimeException pdo mysql extension is not installed
86
     */
87
    public function getConnection(OutputInterface $output = null)
88
    {
89
        if ($output == null) {
90
            $output = new NullOutput();
91
        }
92
93
        if ($this->_connection) {
94
            return $this->_connection;
95
        }
96
97
        $this->detectDbSettings($output);
98
99
        if (!extension_loaded('pdo_mysql')) {
100
            throw new RuntimeException('pdo_mysql extension is not installed');
101
        }
102
103
        if (strpos($this->dbSettings['host'], '/') !== false) {
104
            $this->dbSettings['unix_socket'] = $this->dbSettings['host'];
105
            unset($this->dbSettings['host']);
106 View Code Duplication
        } elseif (strpos($this->dbSettings['host'], ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
            list($this->dbSettings['host'], $this->dbSettings['port']) = explode(':', $this->dbSettings['host']);
108
        }
109
110
        $this->_connection = new PDO(
111
            $this->dsn(),
112
            $this->dbSettings['username'],
113
            $this->dbSettings['password']
114
        );
115
116
        /** @link http://bugs.mysql.com/bug.php?id=18551 */
117
        $this->_connection->query("SET SQL_MODE=''");
118
119
        try {
120
            $this->_connection->query('USE `' . $this->dbSettings['dbname'] . '`');
121
        } catch (PDOException $e) {
122
            if (OutputInterface::VERBOSITY_VERY_VERBOSE <= $output->getVerbosity()) {
123
                $output->writeln(sprintf(
124
                    '<error>Failed to use database <comment>%s</comment>: %s</error>',
125
                    var_export($this->dbSettings['dbname'], true),
126
                    $e->getMessage()
127
                ));
128
            }
129
        }
130
131
        $this->_connection->query("SET NAMES utf8");
132
133
        $this->_connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
134
        $this->_connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
135
136
        return $this->_connection;
137
    }
138
139
    /**
140
     * Creates a PDO DSN for the adapter from $this->_config settings.
141
     *
142
     * @see Zend_Db_Adapter_Pdo_Abstract
143
     * @return string
144
     */
145
    public function dsn()
146
    {
147
        $this->detectDbSettings(new NullOutput());
148
149
        // baseline of DSN parts
150
        $dsn = $this->dbSettings;
151
152
        // don't pass the username, password, charset, database, persistent and driver_options in the DSN
153
        unset($dsn['username']);
154
        unset($dsn['password']);
155
        unset($dsn['options']);
156
        unset($dsn['charset']);
157
        unset($dsn['persistent']);
158
        unset($dsn['driver_options']);
159
        unset($dsn['dbname']);
160
161
        // use all remaining parts in the DSN
162
        $buildDsn = array();
163
        foreach ($dsn as $key => $val) {
164
            if (is_array($val)) {
165
                continue;
166
            }
167
            $buildDsn[$key] = "$key=$val";
168
        }
169
170
        return 'mysql:' . implode(';', $buildDsn);
171
    }
172
173
    /**
174
     * Check whether current mysql user has $privilege privilege
175
     *
176
     * @param string $privilege
177
     *
178
     * @return bool
179
     */
180
    public function mysqlUserHasPrivilege($privilege)
181
    {
182
        $statement = $this->getConnection()->query('SHOW GRANTS');
183
184
        $result = $statement->fetchAll(PDO::FETCH_COLUMN);
185
        foreach ($result as $row) {
186
            if (preg_match('/^GRANT(.*)' . strtoupper($privilege) . '/', $row)
187
                || preg_match('/^GRANT(.*)ALL/', $row)
188
            ) {
189
                return true;
190
            }
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * @return string
198
     */
199
    public function getMysqlClientToolConnectionString()
200
    {
201
        $this->detectDbSettings(new NullOutput());
202
203
        if ($this->isSocketConnect) {
204
            $string = '--socket=' . escapeshellarg($this->dbSettings['unix_socket']);
205
        } else {
206
            $string = '-h' . escapeshellarg($this->dbSettings['host']);
207
        }
208
209
        $string .= ' '
210
            . '-u' . escapeshellarg($this->dbSettings['username'])
211
            . ' '
212
            . (isset($this->dbSettings['port'])
213
                ? '-P' . escapeshellarg($this->dbSettings['port']) . ' ' : '')
214
            . (strlen($this->dbSettings['password'])
215
                ? '--password=' . escapeshellarg($this->dbSettings['password']) . ' ' : '')
216
            . escapeshellarg($this->dbSettings['dbname']);
217
218
        return $string;
219
    }
220
221
    /**
222
     * Get mysql variable value
223
     *
224
     * @param string $variable
225
     *
226
     * @return bool|string
227
     */
228
    public function getMysqlVariableValue($variable)
229
    {
230
        $statement = $this->getConnection()->query("SELECT @@{$variable};");
231
        $result    = $statement->fetch(PDO::FETCH_ASSOC);
232
        if ($result) {
233
            return $result;
234
        }
235
236
        return false;
237
    }
238
239
    /**
240
     * @param array $commandConfig
241
     *
242
     * @throws \Exception
243
     * @internal param $config
244
     * @return array $commandConfig
245
     * @return array
246
     */
247
    public function getTableDefinitions(array $commandConfig)
248
    {
249
        $tableDefinitions = array();
250
        if (isset($commandConfig['table-groups'])) {
251
            $tableGroups = $commandConfig['table-groups'];
252 View Code Duplication
            foreach ($tableGroups as $index => $definition) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
                $description = isset($definition['description']) ? $definition['description'] : '';
254
                if (!isset($definition['id'])) {
255
                    throw new RuntimeException('Invalid definition of table-groups (id missing) Index: ' . $index);
256
                }
257
                if (!isset($definition['tables'])) {
258
                    throw new RuntimeException(
259
                        'Invalid definition of table-groups (tables missing) Id: ' . $definition['id']
260
                    );
261
                }
262
263
                $tableDefinitions[$definition['id']] = array(
264
                    'tables'      => $definition['tables'],
265
                    'description' => $description,
266
                );
267
            }
268
        }
269
270
        return $tableDefinitions;
271
    }
272
273
    /**
274
     * @param array $list
275
     * @param array $definitions
276
     * @param array $resolved Which definitions where already resolved -> prevent endless loops
277
     *
278
     * @return array
279
     * @throws RuntimeException
280
     */
281
    public function resolveTables(array $list, array $definitions = array(), array $resolved = array())
282
    {
283
        if ($this->_tables === null) {
284
            $this->_tables = $this->getTables(true);
285
        }
286
287
        $resolvedList = array();
288
        foreach ($list as $entry) {
289
            if (substr($entry, 0, 1) == '@') {
290
                $code = substr($entry, 1);
291
                if (!isset($definitions[$code])) {
292
                    throw new RuntimeException('Table-groups could not be resolved: ' . $entry);
293
                }
294
                if (!isset($resolved[$code])) {
295
                    $resolved[$code] = true;
296
                    $tables          = $this->resolveTables(
297
                        explode(' ', $definitions[$code]['tables']),
298
                        $definitions,
299
                        $resolved
300
                    );
301
                    $resolvedList    = array_merge($resolvedList, $tables);
302
                }
303
                continue;
304
            }
305
306
            // resolve wildcards
307
            if (strpos($entry, '*') !== false) {
308
                $connection = $this->getConnection();
309
                $sth        = $connection->prepare(
310
                    'SHOW TABLES LIKE :like',
311
                    array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
312
                );
313
                $sth->execute(
314
                    array(':like' => str_replace('*', '%', $this->dbSettings['prefix'] . $entry))
315
                );
316
                $rows = $sth->fetchAll();
317
                foreach ($rows as $row) {
318
                    $resolvedList[] = $row[0];
319
                }
320
                continue;
321
            }
322
323
            if (in_array($entry, $this->_tables)) {
324
                $resolvedList[] = $this->dbSettings['prefix'] . $entry;
325
            }
326
        }
327
328
        asort($resolvedList);
329
        $resolvedList = array_unique($resolvedList);
330
331
        return $resolvedList;
332
    }
333
334
    /**
335
     * Get list of db tables
336
     *
337
     * @param bool $withoutPrefix
338
     *
339
     * @return array
340
     */
341
    public function getTables($withoutPrefix = false)
342
    {
343
        $db     = $this->getConnection();
344
        $prefix = $this->dbSettings['prefix'];
345 View Code Duplication
        if (strlen($prefix) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
            $statement = $db->prepare('SHOW TABLES LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
347
            $statement->execute(
348
                array(':like' => $prefix . '%')
349
            );
350
        } else {
351
            $statement = $db->query('SHOW TABLES');
352
        }
353
354
        if ($statement) {
355
            $result = $statement->fetchAll(PDO::FETCH_COLUMN);
356
            if ($withoutPrefix === false) {
357
                return $result;
358
            }
359
360
            return array_map(function ($tableName) use ($prefix) {
361
                return str_replace($prefix, '', $tableName);
362
            }, $result);
363
        }
364
365
        return array();
366
    }
367
368
    /**
369
     * Get list of db tables status
370
     *
371
     * @param bool $withoutPrefix
372
     *
373
     * @return array
374
     */
375
    public function getTablesStatus($withoutPrefix = false)
376
    {
377
        $db     = $this->getConnection();
378
        $prefix = $this->dbSettings['prefix'];
379 View Code Duplication
        if (strlen($prefix) > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
380
            $statement = $db->prepare('SHOW TABLE STATUS LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
381
            $statement->execute(
382
                array(':like' => $prefix . '%')
383
            );
384
        } else {
385
            $statement = $db->query('SHOW TABLE STATUS');
386
        }
387
388
        if ($statement) {
389
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
390
            $return = array();
391
            foreach ($result as $table) {
392
                if (true === $withoutPrefix) {
393
                    $table['Name'] = str_replace($prefix, '', $table['Name']);
394
                }
395
                $return[$table['Name']] = $table;
396
            }
397
            return $return;
398
        }
399
400
        return array();
401
    }
402
403
    /**
404
     * @return array
405
     */
406
    public function getDbSettings()
407
    {
408
        return $this->dbSettings;
409
    }
410
411
    /**
412
     * @return boolean
413
     */
414
    public function getIsSocketConnect()
415
    {
416
        return $this->isSocketConnect;
417
    }
418
419
    /**
420
     * Returns the canonical name of this helper.
421
     *
422
     * @return string The canonical name
423
     *
424
     * @api
425
     */
426
    public function getName()
427
    {
428
        return 'database';
429
    }
430
431
    /**
432
     * @param OutputInterface $output
433
     */
434 View Code Duplication
    public function dropDatabase($output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
435
    {
436
        $this->detectDbSettings($output);
437
        $db = $this->getConnection();
438
        $db->query('DROP DATABASE `' . $this->dbSettings['dbname'] . '`');
439
        $output->writeln('<info>Dropped database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
440
    }
441
442
    /**
443
     * @param OutputInterface $output
444
     */
445
    public function dropTables($output)
446
    {
447
        $result = $this->getTables();
448
        $query  = 'SET FOREIGN_KEY_CHECKS = 0; ';
449
        $count  = 0;
450
        foreach ($result as $tableName) {
451
            $query .= 'DROP TABLE IF EXISTS `' . $tableName . '`; ';
452
            $count++;
453
        }
454
        $query .= 'SET FOREIGN_KEY_CHECKS = 1;';
455
        $this->getConnection()->query($query);
456
        $output->writeln('<info>Dropped database tables</info> <comment>' . $count . ' tables dropped</comment>');
457
    }
458
459
    /**
460
     * @param OutputInterface $output
461
     */
462 View Code Duplication
    public function createDatabase($output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
463
    {
464
        $this->detectDbSettings($output);
465
        $db = $this->getConnection();
466
        $db->query('CREATE DATABASE IF NOT EXISTS `' . $this->dbSettings['dbname'] . '`');
467
        $output->writeln('<info>Created database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
468
    }
469
470
    /**
471
     * @param string $command  example: 'VARIABLES', 'STATUS'
472
     * @param string $variable [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $variable not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
473
     *
474
     * @return array
475
     */
476
    private function runShowCommand($command, $variable = null)
477
    {
478
        $db = $this->getConnection();
479
480
        if (null !== $variable) {
481
            $statement = $db->prepare(
482
                'SHOW /*!50000 GLOBAL */ ' . $command . ' LIKE :like',
483
                array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
484
            );
485
            $statement->execute(
486
                array(':like' => $variable)
487
            );
488
        } else {
489
            $statement = $db->query('SHOW /*!50000 GLOBAL */ ' . $command);
490
        }
491
492
        if ($statement) {
493
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
494
            $return = array();
495
            foreach ($result as $row) {
496
                $return[$row['Variable_name']] = $row['Value'];
497
            }
498
            return $return;
499
        }
500
501
        return array();
502
    }
503
504
    /**
505
     * @param string $variable [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $variable not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
506
     *
507
     * @return array
508
     */
509
    public function getGlobalVariables($variable = null)
510
    {
511
        return $this->runShowCommand('VARIABLES', $variable);
512
    }
513
514
    /**
515
     * @param string $variable [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $variable not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
516
     *
517
     * @return array
518
     */
519
    public function getGlobalStatus($variable = null)
520
    {
521
        return $this->runShowCommand('STATUS', $variable);
522
    }
523
}
524