Completed
Push — master ( bd5921...d9293e )
by Christian
17:54 queued 08:59
created

src/N98/Util/Console/Helper/DatabaseHelper.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
53
            return;
54
        }
55
56
        $application = $this->getApplication();
57
        $application->detectMagento();
58
59
        $configFile = $application->getMagentoRootFolder() . '/app/etc/local.xml';
60
61
        if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
62
            $output->writeln(sprintf('<debug>Loading database configuration from file <info>%s</info></debug>', $configFile));
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
63
        }
64
65
        try {
66
            $this->dbSettings = new DbSettings($configFile);
67
        } catch (InvalidArgumentException $e) {
68
            $output->writeln('<error>' . $e->getMessage() . '</error>');
69
            throw new RuntimeException('Failed to load database settings from config file', 0, $e);
70
        }
71
    }
72
73
    /**
74
     * Connects to the database without initializing magento
75
     *
76
     * @param OutputInterface $output = null
77
     *
78
     * @return PDO
79
     */
80
    public function getConnection(OutputInterface $output = null)
81
    {
82
        if (!$this->_connection) {
83
            $this->_connection = $this->getDbSettings($output)->getConnection();
84
        }
85
86
        return $this->_connection;
87
    }
88
89
    /**
90
     * Creates a PDO DSN for the adapter from $this->_config settings.
91
     *
92
     * @see Zend_Db_Adapter_Pdo_Abstract
93
     * @return string
94
     */
95
    public function dsn()
96
    {
97
        return $this->getDbSettings()->getDsn();
98
    }
99
100
    /**
101
     * Check whether current mysql user has $privilege privilege
102
     *
103
     * @param string $privilege
104
     *
105
     * @return bool
106
     */
107
    public function mysqlUserHasPrivilege($privilege)
108
    {
109
        $statement = $this->getConnection()->query('SHOW GRANTS');
110
111
        $result = $statement->fetchAll(PDO::FETCH_COLUMN);
112
        foreach ($result as $row) {
113
            if (preg_match('/^GRANT(.*)' . strtoupper($privilege) . '/', $row)
114
                || preg_match('/^GRANT(.*)ALL/', $row)
115
            ) {
116
                return true;
117
            }
118
        }
119
120
        return false;
121
    }
122
123
    /**
124
     * @return string
125
     */
126
    public function getMysqlClientToolConnectionString()
127
    {
128
        return $this->getDbSettings()->getMysqlClientToolConnectionString();
129
    }
130
131
    /**
132
     * Get mysql variable value
133
     *
134
     * @param string $variable
135
     *
136
     * @return bool|array returns array on success, false on failure
137
     */
138
    public function getMysqlVariableValue($variable)
139
    {
140
        $statement = $this->getConnection()->query("SELECT @@{$variable};");
141
        if (false === $statement) {
142
            throw new RuntimeException(sprintf('Failed to query mysql variable %s', var_export($variable, 1)));
143
        }
144
145
        $result = $statement->fetch(PDO::FETCH_ASSOC);
146
        if ($result) {
147
            return $result;
148
        }
149
150
        return false;
151
    }
152
153
    /**
154
     * obtain mysql variable value from the database connection.
155
     *
156
     * in difference to @see getMysqlVariableValue(), this method allows to specify the type of the variable as well
157
     * as to use any variable identifier even such that need quoting.
158
     *
159
     * @param string $name mysql variable name
160
     * @param string $type [optional] variable type, can be a system variable ("@@", default) or a session variable
161
     *                     ("@").
162
     *
163
     * @return string variable value, null if variable was not defined
164
     * @throws RuntimeException in case a system variable is unknown (SQLSTATE[HY000]: 1193: Unknown system variable
165
     *                          'nonexistent')
166
     */
167
    public function getMysqlVariable($name, $type = null)
168
    {
169
        if (null === $type) {
170
            $type = "@@";
171
        } else {
172
            $type = (string) $type;
173
        }
174
175
        if (!in_array($type, array("@@", "@"), true)) {
176
            throw new InvalidArgumentException(
177
                sprintf('Invalid mysql variable type "%s", must be "@@" (system) or "@" (session)', $type)
178
            );
179
        }
180
181
        $quoted = '`' . strtr($name, array('`' => '``')) . '`';
182
        $query  = "SELECT {$type}{$quoted};";
183
184
        $connection = $this->getConnection();
185
        $statement  = $connection->query($query, PDO::FETCH_COLUMN, 0);
186
        if ($statement instanceof PDOStatement) {
187
            $result = $statement->fetchColumn(0);
188
        } else {
189
            $reason = $connection->errorInfo()
190
                ? vsprintf('SQLSTATE[%s]: %s: %s', $connection->errorInfo())
191
                : 'no error info';
192
193
            throw new RuntimeException(
194
                sprintf('Failed to query mysql variable %s: %s', var_export($name, true), $reason)
195
            );
196
        }
197
198
        return $result;
199
    }
200
201
    /**
202
     * @param array $commandConfig
203
     *
204
     * @return array
205
     */
206
    public function getTableDefinitions(array $commandConfig)
207
    {
208
        $tableDefinitions = array();
209
        if (isset($commandConfig['table-groups'])) {
210
            $tableGroups = $commandConfig['table-groups'];
211
            foreach ($tableGroups as $index => $definition) {
212
                $description = isset($definition['description']) ? $definition['description'] : '';
213
                if (!isset($definition['id'])) {
214
                    throw new RuntimeException('Invalid definition of table-groups (id missing) Index: ' . $index);
215
                }
216
                if (!isset($definition['tables'])) {
217
                    throw new RuntimeException('Invalid definition of table-groups (tables missing) Id: '
218
                        . $definition['id']
219
                    );
220
                }
221
222
                $tableDefinitions[$definition['id']] = array(
223
                    'tables'      => $definition['tables'],
224
                    'description' => $description,
225
                );
226
            }
227
        }
228
229
        return $tableDefinitions;
230
    }
231
232
    /**
233
     * @param array $list
234
     * @param array $definitions
235
     * @param array $resolved Which definitions where already resolved -> prevent endless loops
236
     *
237
     * @return array
238
     * @throws RuntimeException
239
     */
240
    public function resolveTables(array $list, array $definitions = array(), array $resolved = array())
241
    {
242
        if ($this->_tables === null) {
243
            $this->_tables = $this->getTables(true);
244
        }
245
246
        $resolvedList = array();
247
        foreach ($list as $entry) {
248
            if (substr($entry, 0, 1) == '@') {
249
                $code = substr($entry, 1);
250
                if (!isset($definitions[$code])) {
251
                    throw new RuntimeException('Table-groups could not be resolved: ' . $entry);
252
                }
253
                if (!isset($resolved[$code])) {
254
                    $resolved[$code] = true;
255
                    $tables          = $this->resolveTables(explode(' ', $definitions[$code]['tables']), $definitions, $resolved);
256
                    $resolvedList    = array_merge($resolvedList, $tables);
257
                }
258
                continue;
259
            }
260
261
            // resolve wildcards
262
            if (strpos($entry, '*') !== false) {
263
                $connection = $this->getConnection();
264
                $sth        = $connection->prepare('SHOW TABLES LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
265
                $sth->execute(
266
                    array(':like' => str_replace('*', '%', $this->dbSettings['prefix'] . $entry))
267
                );
268
                $rows = $sth->fetchAll();
269
                foreach ($rows as $row) {
270
                    $resolvedList[] = $row[0];
271
                }
272
                continue;
273
            }
274
275
            if (in_array($entry, $this->_tables)) {
276
                $resolvedList[] = $this->dbSettings['prefix'] . $entry;
277
            }
278
        }
279
280
        asort($resolvedList);
281
        $resolvedList = array_unique($resolvedList);
282
283
        return $resolvedList;
284
    }
285
286
    /**
287
     * Get list of database tables
288
     *
289
     * @param bool $withoutPrefix [optional] remove prefix from the returned table names. prefix is obtained from
290
     *                            magento database configuration. defaults to false.
291
     *
292
     * @return array
293
     * @throws RuntimeException
294
     */
295
    public function getTables($withoutPrefix = null)
296
    {
297
        $withoutPrefix = (bool) $withoutPrefix;
298
299
        $db     = $this->getConnection();
300
        $prefix = $this->dbSettings['prefix'];
301
        $length = strlen($prefix);
302
303
        $columnName = 'table_name';
304
        $column     = $columnName;
305
306
        $input = array();
307
308
        if ($withoutPrefix && $length) {
309
            $column         = sprintf('SUBSTRING(%1$s FROM 1 + CHAR_LENGTH(:name)) %1$s', $columnName);
310
            $input[':name'] = $prefix;
311
        }
312
313
        $condition = 'table_schema = database()';
314
315
        if ($length) {
316
            $escape = '=';
317
            $condition .= sprintf(" AND %s LIKE :like ESCAPE '%s'", $columnName, $escape);
318
            $input[':like'] = $this->quoteLike($prefix, $escape) . '%';
319
        }
320
321
        $query     = sprintf('SELECT %s FROM information_schema.tables WHERE %s;', $column, $condition);
322
        $statement = $db->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
323
        $result    = $statement->execute($input);
324
325
        if (!$result) {
326
            // @codeCoverageIgnoreStart
327
            $this->throwRuntimeException(
328
                $statement
329
                , sprintf('Failed to obtain tables from database: %s', var_export($query, true))
330
            );
331
        } // @codeCoverageIgnoreEnd
332
333
        $result = $statement->fetchAll(PDO::FETCH_COLUMN, 0);
334
335
        return $result;
336
    }
337
338
    /**
339
     * throw a runtime exception and provide error info for the statement if available
340
     *
341
     * @param PDOStatement $statement
342
     * @param string       $message
343
     *
344
     * @throws RuntimeException
345
     */
346
    private function throwRuntimeException(PDOStatement $statement, $message = "")
347
    {
348
        $reason = $statement->errorInfo()
349
            ? vsprintf('SQLSTATE[%s]: %s: %s', $statement->errorInfo())
350
            : 'no error info for statement';
351
352
        if (strlen($message)) {
353
            $message .= ': ';
354
        } else {
355
            $message = '';
356
        }
357
358
        throw new RuntimeException($message . $reason);
359
    }
360
361
    /**
362
     * quote a string so that it is safe to use in a LIKE
363
     *
364
     * @param string $string
365
     * @param string $escape character - single us-ascii character
366
     *
367
     * @return string
368
     */
369
    private function quoteLike($string, $escape = '=')
370
    {
371
        $translation = array(
372
            $escape => $escape . $escape,
373
            '%'     => $escape . '%',
374
            '_'     => $escape . '_',
375
        );
376
377
        return strtr($string, $translation);
378
    }
379
380
    /**
381
     * Get list of db tables status
382
     *
383
     * @param bool $withoutPrefix
384
     *
385
     * @return array
386
     */
387
    public function getTablesStatus($withoutPrefix = false)
388
    {
389
        $db     = $this->getConnection();
390
        $prefix = $this->dbSettings['prefix'];
391
        if (strlen($prefix) > 0) {
392
            $statement = $db->prepare('SHOW TABLE STATUS LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
393
            $statement->execute(
394
                array(':like' => $prefix . '%')
395
            );
396
        } else {
397
            $statement = $db->query('SHOW TABLE STATUS');
398
        }
399
400
        if ($statement) {
401
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
402
            $return = array();
403
            foreach ($result as $table) {
404
                if (true === $withoutPrefix) {
405
                    $table['Name'] = str_replace($prefix, '', $table['Name']);
406
                }
407
                $return[$table['Name']] = $table;
408
            }
409
410
            return $return;
411
        }
412
413
        return array();
414
    }
415
416
    /**
417
     * @param OutputInterface $output [optional]
418
     *
419
     * @return array|DbSettings
420
     */
421
    public function getDbSettings(OutputInterface $output = null)
422
    {
423
        if ($this->dbSettings) {
424
            return $this->dbSettings;
425
        }
426
427
        $output = $this->fallbackOutput($output);
428
429
        $this->detectDbSettings($output);
430
431
        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...
432
            throw new RuntimeException('Database settings fatal error');
433
        }
434
435
        return $this->dbSettings;
436
    }
437
438
    /**
439
     * @return boolean
440
     */
441
    public function getIsSocketConnect()
442
    {
443
        return $this->getDbSettings()->isSocketConnect();
444
    }
445
446
    /**
447
     * Returns the canonical name of this helper.
448
     *
449
     * @return string The canonical name
450
     *
451
     * @api
452
     */
453
    public function getName()
454
    {
455
        return 'database';
456
    }
457
458
    /**
459
     * @param OutputInterface $output
460
     */
461 View Code Duplication
    public function dropDatabase($output)
462
    {
463
        $this->detectDbSettings($output);
464
        $db = $this->getConnection();
465
        $db->query('DROP DATABASE `' . $this->dbSettings['dbname'] . '`');
466
        $output->writeln('<info>Dropped database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
467
    }
468
469
    /**
470
     * @param OutputInterface $output
471
     */
472
    public function dropTables($output)
473
    {
474
        $result = $this->getTables();
475
        $query  = 'SET FOREIGN_KEY_CHECKS = 0; ';
476
        $count  = 0;
477
        foreach ($result as $tableName) {
478
            $query .= 'DROP TABLE IF EXISTS `' . $tableName . '`; ';
479
            $count++;
480
        }
481
        $query .= 'SET FOREIGN_KEY_CHECKS = 1;';
482
        $this->getConnection()->query($query);
483
        $output->writeln('<info>Dropped database tables</info> <comment>' . $count . ' tables dropped</comment>');
484
    }
485
486
    /**
487
     * @param OutputInterface $output
488
     */
489 View Code Duplication
    public function createDatabase($output)
490
    {
491
        $this->detectDbSettings($output);
492
        $db = $this->getConnection();
493
        $db->query('CREATE DATABASE IF NOT EXISTS `' . $this->dbSettings['dbname'] . '`');
494
        $output->writeln('<info>Created database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>');
495
    }
496
497
    /**
498
     * @param string $command  example: 'VARIABLES', 'STATUS'
499
     * @param string $variable [optional]
500
     *
501
     * @return array
502
     */
503
    private function runShowCommand($command, $variable = null)
504
    {
505
        $db = $this->getConnection();
506
507
        if (null !== $variable) {
508
            $statement = $db->prepare(
509
                'SHOW /*!50000 GLOBAL */ ' . $command . ' LIKE :like',
510
                array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)
511
            );
512
            $statement->execute(
513
                array(':like' => $variable)
514
            );
515
        } else {
516
            $statement = $db->query('SHOW /*!50000 GLOBAL */ ' . $command);
517
        }
518
519
        if ($statement) {
520
            /** @var array|string[] $result */
521
            $result = $statement->fetchAll(PDO::FETCH_ASSOC);
522
            $return = array();
523
            foreach ($result as $row) {
524
                $return[$row['Variable_name']] = $row['Value'];
525
            }
526
527
            return $return;
528
        }
529
530
        return array();
531
    }
532
533
    /**
534
     * @param string $variable [optional]
535
     *
536
     * @return array
537
     */
538
    public function getGlobalVariables($variable = null)
539
    {
540
        return $this->runShowCommand('VARIABLES', $variable);
541
    }
542
543
    /**
544
     * @param string $variable [optional]
545
     *
546
     * @return array
547
     */
548
    public function getGlobalStatus($variable = null)
549
    {
550
        return $this->runShowCommand('STATUS', $variable);
551
    }
552
553
    /**
554
     * @return Application|BaseApplication
555
     */
556
    private function getApplication()
557
    {
558
        $command = $this->getHelperSet()->getCommand();
559
560
        if ($command) {
561
            $application = $command->getApplication();
562
        } else {
563
            $application = new Application();
564
        }
565
566
        return $application;
567
    }
568
569
    /**
570
     * small helper method to obtain an object of type OutputInterface
571
     *
572
     * @param OutputInterface|null $output
573
     *
574
     * @return OutputInterface
575
     */
576
    private function fallbackOutput(OutputInterface $output = null)
577
    {
578
        if (null !== $output) {
579
            return $output;
580
        }
581
582
        if ($helper = $this->getHelperSet()->get('io')) {
583
            /** @var $helper IoHelper */
584
            $output = $helper->getOutput();
585
        }
586
587
        if (null === $output) {
588
            $output = new NullOutput();
589
        }
590
591
        return $output;
592
    }
593
}
594