Completed
Push — develop ( 6f71dd...40edc3 )
by Tom
11s
created

DatabaseHelper::resolveTables()   C

Complexity

Conditions 9
Paths 16

Size

Total Lines 52
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 52
rs 6.5703
cc 9
eloc 33
nc 16
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace N98\Util\Console\Helper;
4
5
use InvalidArgumentException;
6
use N98\Magento\DbSettings;
7
use PDO;
8
use PDOStatement;
9
use RuntimeException;
10
use Symfony\Component\Console\Application as BaseApplication;
11
use Symfony\Component\Console\Helper\Helper as AbstractHelper;
12
use N98\Magento\Application;
13
use Symfony\Component\Console\Output\NullOutput;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
/**
17
 * Class DatabaseHelper
18
 *
19
 * @package N98\Util\Console\Helper
20
 */
21
class DatabaseHelper extends AbstractHelper
22
{
23
    /**
24
     * @var array|DbSettings
25
     */
26
    protected $dbSettings = null;
27
28
    /**
29
     * @var bool
30
     * @deprecated since 1.97.9, use $dbSettings->isSocketConnect()
31
     */
32
    protected $isSocketConnect = false;
33
34
    /**
35
     * @var PDO
36
     */
37
    protected $_connection = null;
38
39
    /**
40
     * @var array
41
     */
42
    protected $_tables;
43
44
    /**
45
     * @param OutputInterface $output
46
     *
47
     * @throws RuntimeException
48
     */
49
    public function detectDbSettings(OutputInterface $output)
50
    {
51
        if (null !== $this->dbSettings) {
52
            return;
53
        }
54
55
        $application = $this->getApplication();
56
        $application->detectMagento();
57
58
        $configFile = $application->getMagentoRootFolder() . '/app/etc/local.xml';
59
60
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
61
            $output->writeln(
62
                sprintf('<debug>Loading database configuration from file <info>%s</info></debug>', $configFile)
63
            );
64
        }
65
66
        try {
67
            $this->dbSettings = new DbSettings($configFile);
68
        } catch (InvalidArgumentException $e) {
69
            $output->writeln('<error>' . $e->getMessage() . '</error>');
70
            throw new RuntimeException('Failed to load database settings from config file', 0, $e);
71
        }
72
    }
73
74
    /**
75
     * Connects to the database without initializing magento
76
     *
77
     * @param OutputInterface $output = null
78
     *
79
     * @return PDO
80
     */
81
    public function getConnection(OutputInterface $output = null)
82
    {
83
        if (!$this->_connection) {
84
            $this->_connection = $this->getDbSettings($output)->getConnection();
85
        }
86
87
        return $this->_connection;
88
    }
89
90
    /**
91
     * Creates a PDO DSN for the adapter from $this->_config settings.
92
     *
93
     * @see Zend_Db_Adapter_Pdo_Abstract
94
     * @return string
95
     */
96
    public function dsn()
97
    {
98
        return $this->getDbSettings()->getDsn();
99
    }
100
101
    /**
102
     * Check whether current mysql user has $privilege privilege
103
     *
104
     * @param string $privilege
105
     *
106
     * @return bool
107
     */
108
    public function mysqlUserHasPrivilege($privilege)
109
    {
110
        $statement = $this->getConnection()->query('SHOW GRANTS');
111
112
        $result = $statement->fetchAll(PDO::FETCH_COLUMN);
113
        foreach ($result as $row) {
114
            if (preg_match('/^GRANT(.*)' . strtoupper($privilege) . '/', $row)
115
                || preg_match('/^GRANT(.*)ALL/', $row)
116
            ) {
117
                return true;
118
            }
119
        }
120
121
        return false;
122
    }
123
124
    /**
125
     * @return string
126
     */
127
    public function getMysqlClientToolConnectionString()
128
    {
129
        return $this->getDbSettings()->getMysqlClientToolConnectionString();
130
    }
131
132
    /**
133
     * Get mysql variable value
134
     *
135
     * @param string $variable
136
     *
137
     * @return bool|array returns array on success, false on failure
138
     */
139
    public function getMysqlVariableValue($variable)
140
    {
141
        $statement = $this->getConnection()->query("SELECT @@{$variable};");
142
        if (false === $statement) {
143
            throw new RuntimeException(sprintf('Failed to query mysql variable %s', var_export($variable, 1)));
144
        }
145
146
        $result = $statement->fetch(PDO::FETCH_ASSOC);
147
        if ($result) {
148
            return $result;
149
        }
150
151
        return false;
152
    }
153
154
    /**
155
     * obtain mysql variable value from the database connection.
156
     *
157
     * in difference to @see getMysqlVariableValue(), this method allows to specify the type of the variable as well
158
     * as to use any variable identifier even such that need quoting.
159
     *
160
     * @param string $name mysql variable name
161
     * @param string $type [optional] variable type, can be a system variable ("@@", default) or a session variable
162
     *                     ("@").
163
     *
164
     * @return string variable value, null if variable was not defined
165
     * @throws RuntimeException in case a system variable is unknown (SQLSTATE[HY000]: 1193: Unknown system variable
166
     *                          'nonexistent')
167
     */
168
    public function getMysqlVariable($name, $type = null)
169
    {
170
        if (null === $type) {
171
            $type = "@@";
172
        } else {
173
            $type = (string) $type;
174
        }
175
176
        if (!in_array($type, array("@@", "@"), true)) {
177
            throw new InvalidArgumentException(
178
                sprintf('Invalid mysql variable type "%s", must be "@@" (system) or "@" (session)', $type)
179
            );
180
        }
181
182
        $quoted = '`' . strtr($name, array('`' => '``')) . '`';
183
        $query  = "SELECT {$type}{$quoted};";
184
185
        $connection = $this->getConnection();
186
        $statement  = $connection->query($query, PDO::FETCH_COLUMN, 0);
187
        if ($statement instanceof PDOStatement) {
188
            $result = $statement->fetchColumn(0);
189
        } else {
190
            $reason = $connection->errorInfo()
191
                ? vsprintf('SQLSTATE[%s]: %s: %s', $connection->errorInfo())
192
                : 'no error info';
193
194
            throw new RuntimeException(
195
                sprintf('Failed to query mysql variable %s: %s', var_export($name, true), $reason)
196
            );
197
        }
198
199
        return $result;
200
    }
201
202
    /**
203
     * @param array $commandConfig
204
     *
205
     * @return array
206
     */
207
    public function getTableDefinitions(array $commandConfig)
208
    {
209
        $tableDefinitions = array();
210
        if (isset($commandConfig['table-groups'])) {
211
            $tableGroups = $commandConfig['table-groups'];
212
            foreach ($tableGroups as $index => $definition) {
213
                $description = isset($definition['description']) ? $definition['description'] : '';
214
                if (!isset($definition['id'])) {
215
                    throw new RuntimeException('Invalid definition of table-groups (id missing) Index: ' . $index);
216
                }
217
                if (!isset($definition['tables'])) {
218
                    throw new RuntimeException('Invalid definition of table-groups (tables missing) Id: '
219
                        . $definition['id']
220
                    );
221
                }
222
223
                $tableDefinitions[$definition['id']] = array(
224
                    'tables'      => $definition['tables'],
225
                    'description' => $description,
226
                );
227
            }
228
        }
229
230
        return $tableDefinitions;
231
    }
232
233
    /**
234
     * @param array $list
235
     * @param array $definitions
236
     * @param array $resolved Which definitions where already resolved -> prevent endless loops
237
     *
238
     * @return array
239
     * @throws RuntimeException
240
     */
241
    public function resolveTables(array $list, array $definitions = array(), array $resolved = array())
242
    {
243
        if ($this->_tables === null) {
244
            $this->_tables = $this->getTables(true);
245
        }
246
247
        $resolvedList = array();
248
        foreach ($list as $entry) {
249
            if (substr($entry, 0, 1) == '@') {
250
                $code = substr($entry, 1);
251
                if (!isset($definitions[$code])) {
252
                    throw new RuntimeException('Table-groups could not be resolved: ' . $entry);
253
                }
254
                if (!isset($resolved[$code])) {
255
                    $resolved[$code] = true;
256
                    $tables          = $this->resolveTables(
257
                        explode(' ', $definitions[$code]['tables']),
258
                        $definitions,
259
                        $resolved
260
                    );
261
                    $resolvedList    = array_merge($resolvedList, $tables);
262
                }
263
                continue;
264
            }
265
266
            // resolve wildcards
267
            if (strpos($entry, '*') !== false) {
268
                $connection = $this->getConnection();
269
                $sth        = $connection->prepare(
270
                    'SHOW TABLES LIKE :like',
271
                    array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
272
                );
273
                $sth->execute(
274
                    array(':like' => str_replace('*', '%', $this->dbSettings['prefix'] . $entry))
275
                );
276
                $rows = $sth->fetchAll();
277
                foreach ($rows as $row) {
278
                    $resolvedList[] = $row[0];
279
                }
280
                continue;
281
            }
282
283
            if (in_array($entry, $this->_tables)) {
284
                $resolvedList[] = $this->dbSettings['prefix'] . $entry;
285
            }
286
        }
287
288
        asort($resolvedList);
289
        $resolvedList = array_unique($resolvedList);
290
291
        return $resolvedList;
292
    }
293
294
    /**
295
     * Get list of database tables
296
     *
297
     * @param bool $withoutPrefix [optional] remove prefix from the returned table names. prefix is obtained from
298
     *                            magento database configuration. defaults to false.
299
     *
300
     * @return array
301
     * @throws RuntimeException
302
     */
303
    public function getTables($withoutPrefix = null)
304
    {
305
        $withoutPrefix = (bool) $withoutPrefix;
306
307
        $db     = $this->getConnection();
308
        $prefix = $this->dbSettings['prefix'];
309
        $length = strlen($prefix);
310
311
        $columnName = 'table_name';
312
        $column     = $columnName;
313
314
        $input = array();
315
316
        if ($withoutPrefix && $length) {
317
            $column         = sprintf('SUBSTRING(%1$s FROM 1 + CHAR_LENGTH(:name)) %1$s', $columnName);
318
            $input[':name'] = $prefix;
319
        }
320
321
        $condition = 'table_schema = database()';
322
323
        if ($length) {
324
            $escape = '=';
325
            $condition .= sprintf(" AND %s LIKE :like ESCAPE '%s'", $columnName, $escape);
326
            $input[':like'] = $this->quoteLike($prefix, $escape) . '%';
327
        }
328
329
        $query     = sprintf('SELECT %s FROM information_schema.tables WHERE %s;', $column, $condition);
330
        $statement = $db->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
331
        $result    = $statement->execute($input);
332
333
        if (!$result) {
334
            // @codeCoverageIgnoreStart
335
            $this->throwRuntimeException(
336
                $statement, sprintf('Failed to obtain tables from database: %s', var_export($query, true))
337
            );
338
        } // @codeCoverageIgnoreEnd
339
340
        $result = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
341
342
        return $result;
343
    }
344
345
    /**
346
     * throw a runtime exception and provide error info for the statement if available
347
     *
348
     * @param PDOStatement $statement
349
     * @param string       $message
350
     *
351
     * @throws RuntimeException
352
     */
353
    private function throwRuntimeException(PDOStatement $statement, $message = "")
354
    {
355
        $reason = $statement->errorInfo()
356
            ? vsprintf('SQLSTATE[%s]: %s: %s', $statement->errorInfo())
357
            : 'no error info for statement';
358
359
        if (strlen($message)) {
360
            $message .= ': ';
361
        } else {
362
            $message = '';
363
        }
364
365
        throw new RuntimeException($message . $reason);
366
    }
367
368
    /**
369
     * quote a string so that it is safe to use in a LIKE
370
     *
371
     * @param string $string
372
     * @param string $escape character - single us-ascii character
373
     *
374
     * @return string
375
     */
376
    private function quoteLike($string, $escape = '=')
377
    {
378
        $translation = array(
379
            $escape => $escape . $escape,
380
            '%'     => $escape . '%',
381
            '_'     => $escape . '_',
382
        );
383
384
        return strtr($string, $translation);
385
    }
386
387
    /**
388
     * Get list of db tables status
389
     *
390
     * @param bool $withoutPrefix
391
     *
392
     * @return array
393
     */
394
    public function getTablesStatus($withoutPrefix = false)
395
    {
396
        $db     = $this->getConnection();
397
        $prefix = $this->dbSettings['prefix'];
398
        if (strlen($prefix) > 0) {
399
            $statement = $db->prepare('SHOW TABLE STATUS LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
400
            $statement->execute(
401
                array(':like' => $prefix . '%')
402
            );
403
        } else {
404
            $statement = $db->query('SHOW TABLE STATUS');
405
        }
406
407
        if ($statement) {
408
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
409
            $return = array();
410
            foreach ($result as $table) {
411
                if (true === $withoutPrefix) {
412
                    $table['Name'] = str_replace($prefix, '', $table['Name']);
413
                }
414
                $return[$table['Name']] = $table;
415
            }
416
417
            return $return;
418
        }
419
420
        return array();
421
    }
422
423
    /**
424
     * @param OutputInterface $output [optional]
425
     *
426
     * @return array|DbSettings
427
     */
428
    public function getDbSettings(OutputInterface $output = null)
429
    {
430
        if ($this->dbSettings) {
431
            return $this->dbSettings;
432
        }
433
434
        $output = $this->fallbackOutput($output);
435
436
        $this->detectDbSettings($output);
437
438
        if (!$this->dbSettings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->dbSettings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
439
            throw new RuntimeException('Database settings fatal error');
440
        }
441
442
        return $this->dbSettings;
443
    }
444
445
    /**
446
     * @return boolean
447
     */
448
    public function getIsSocketConnect()
449
    {
450
        return $this->getDbSettings()->isSocketConnect();
451
    }
452
453
    /**
454
     * Returns the canonical name of this helper.
455
     *
456
     * @return string The canonical name
457
     *
458
     * @api
459
     */
460
    public function getName()
461
    {
462
        return 'database';
463
    }
464
465
    /**
466
     * @param OutputInterface $output
467
     */
468 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...
469
    {
470
        $this->detectDbSettings($output);
471
        $db = $this->getConnection();
472
        $db->query('DROP DATABASE `' . $this->dbSettings['dbname'] . '`');
473
        $output->writeln('<info>Dropped database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
474
    }
475
476
    /**
477
     * @param OutputInterface $output
478
     */
479
    public function dropTables($output)
480
    {
481
        $result = $this->getTables();
482
        $query  = 'SET FOREIGN_KEY_CHECKS = 0; ';
483
        $count  = 0;
484
        foreach ($result as $tableName) {
485
            $query .= 'DROP TABLE IF EXISTS `' . $tableName . '`; ';
486
            $count++;
487
        }
488
        $query .= 'SET FOREIGN_KEY_CHECKS = 1;';
489
        $this->getConnection()->query($query);
490
        $output->writeln('<info>Dropped database tables</info> <comment>' . $count . ' tables dropped</comment>');
491
    }
492
493
    /**
494
     * @param OutputInterface $output
495
     */
496 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...
497
    {
498
        $this->detectDbSettings($output);
499
        $db = $this->getConnection();
500
        $db->query('CREATE DATABASE IF NOT EXISTS `' . $this->dbSettings['dbname'] . '`');
501
        $output->writeln('<info>Created database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
502
    }
503
504
    /**
505
     * @param string $command  example: 'VARIABLES', 'STATUS'
506
     * @param string $variable [optional]
507
     *
508
     * @return array
509
     */
510
    private function runShowCommand($command, $variable = null)
511
    {
512
        $db = $this->getConnection();
513
514
        if (null !== $variable) {
515
            $statement = $db->prepare(
516
                'SHOW /*!50000 GLOBAL */ ' . $command . ' LIKE :like',
517
                array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
518
            );
519
            $statement->execute(
520
                array(':like' => $variable)
521
            );
522
        } else {
523
            $statement = $db->query('SHOW /*!50000 GLOBAL */ ' . $command);
524
        }
525
526
        if ($statement) {
527
            /** @var array|string[] $result */
528
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
529
            $return = array();
530
            foreach ($result as $row) {
531
                $return[$row['Variable_name']] = $row['Value'];
532
            }
533
534
            return $return;
535
        }
536
537
        return array();
538
    }
539
540
    /**
541
     * @param string $variable [optional]
542
     *
543
     * @return array
544
     */
545
    public function getGlobalVariables($variable = null)
546
    {
547
        return $this->runShowCommand('VARIABLES', $variable);
548
    }
549
550
    /**
551
     * @param string $variable [optional]
552
     *
553
     * @return array
554
     */
555
    public function getGlobalStatus($variable = null)
556
    {
557
        return $this->runShowCommand('STATUS', $variable);
558
    }
559
560
    /**
561
     * @return Application|BaseApplication
562
     */
563
    private function getApplication()
564
    {
565
        $command = $this->getHelperSet()->getCommand();
566
567
        if ($command) {
568
            $application = $command->getApplication();
569
        } else {
570
            $application = new Application();
571
        }
572
573
        return $application;
574
    }
575
576
    /**
577
     * small helper method to obtain an object of type OutputInterface
578
     *
579
     * @param OutputInterface|null $output
580
     *
581
     * @return OutputInterface
582
     */
583
    private function fallbackOutput(OutputInterface $output = null)
584
    {
585
        if (null !== $output) {
586
            return $output;
587
        }
588
589
        if ($this->getHelperSet()->has('io')) {
590
            $helper = $this->getHelperSet()->get('io');
591
            /** @var $helper IoHelper */
592
            $output = $helper->getOutput();
593
        }
594
595
        if (null === $output) {
596
            $output = new NullOutput();
597
        }
598
599
        return $output;
600
    }
601
}
602