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