Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like DatabaseHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DatabaseHelper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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()) |
||
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, |
||
337 | sprintf('Failed to obtain tables from database: %s', var_export($query, true)) |
||
338 | ); |
||
339 | } // @codeCoverageIgnoreEnd |
||
340 | |||
341 | $result = $statement->fetchAll(PDO::FETCH_COLUMN, 0); |
||
342 | |||
343 | return $result; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * throw a runtime exception and provide error info for the statement if available |
||
348 | * |
||
349 | * @param PDOStatement $statement |
||
350 | * @param string $message |
||
351 | * |
||
352 | * @throws RuntimeException |
||
353 | */ |
||
354 | private function throwRuntimeException(PDOStatement $statement, $message = "") |
||
355 | { |
||
356 | $reason = $statement->errorInfo() |
||
357 | ? vsprintf('SQLSTATE[%s]: %s: %s', $statement->errorInfo()) |
||
358 | : 'no error info for statement'; |
||
359 | |||
360 | if (strlen($message)) { |
||
361 | $message .= ': '; |
||
362 | } else { |
||
363 | $message = ''; |
||
364 | } |
||
365 | |||
366 | throw new RuntimeException($message . $reason); |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * quote a string so that it is safe to use in a LIKE |
||
371 | * |
||
372 | * @param string $string |
||
373 | * @param string $escape character - single us-ascii character |
||
374 | * |
||
375 | * @return string |
||
376 | */ |
||
377 | private function quoteLike($string, $escape = '=') |
||
378 | { |
||
379 | $translation = array( |
||
380 | $escape => $escape . $escape, |
||
381 | '%' => $escape . '%', |
||
382 | '_' => $escape . '_', |
||
383 | ); |
||
384 | |||
385 | return strtr($string, $translation); |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Get list of db tables status |
||
390 | * |
||
391 | * @param bool $withoutPrefix |
||
392 | * |
||
393 | * @return array |
||
394 | */ |
||
395 | public function getTablesStatus($withoutPrefix = false) |
||
396 | { |
||
397 | $db = $this->getConnection(); |
||
398 | $prefix = $this->dbSettings['prefix']; |
||
399 | if (strlen($prefix) > 0) { |
||
400 | $statement = $db->prepare('SHOW TABLE STATUS LIKE :like', array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); |
||
401 | $statement->execute( |
||
402 | array(':like' => $prefix . '%') |
||
403 | ); |
||
404 | } else { |
||
405 | $statement = $db->query('SHOW TABLE STATUS'); |
||
406 | } |
||
407 | |||
408 | if ($statement) { |
||
409 | $result = $statement->fetchAll(PDO::FETCH_ASSOC); |
||
410 | $return = array(); |
||
411 | foreach ($result as $table) { |
||
412 | if (true === $withoutPrefix) { |
||
413 | $table['Name'] = str_replace($prefix, '', $table['Name']); |
||
414 | } |
||
415 | $return[$table['Name']] = $table; |
||
416 | } |
||
417 | |||
418 | return $return; |
||
419 | } |
||
420 | |||
421 | return array(); |
||
422 | } |
||
423 | |||
424 | /** |
||
425 | * @param OutputInterface $output [optional] |
||
426 | * |
||
427 | * @return array|DbSettings |
||
428 | */ |
||
429 | public function getDbSettings(OutputInterface $output = null) |
||
430 | { |
||
431 | if ($this->dbSettings) { |
||
432 | return $this->dbSettings; |
||
433 | } |
||
434 | |||
435 | $output = $this->fallbackOutput($output); |
||
436 | |||
437 | $this->detectDbSettings($output); |
||
438 | |||
439 | if (!$this->dbSettings) { |
||
|
|||
440 | throw new RuntimeException('Database settings fatal error'); |
||
441 | } |
||
442 | |||
443 | return $this->dbSettings; |
||
444 | } |
||
445 | |||
446 | /** |
||
447 | * @return boolean |
||
448 | */ |
||
449 | public function getIsSocketConnect() |
||
450 | { |
||
451 | return $this->getDbSettings()->isSocketConnect(); |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * Returns the canonical name of this helper. |
||
456 | * |
||
457 | * @return string The canonical name |
||
458 | * |
||
459 | * @api |
||
460 | */ |
||
461 | public function getName() |
||
462 | { |
||
463 | return 'database'; |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * @param OutputInterface $output |
||
468 | */ |
||
469 | View Code Duplication | public function dropDatabase($output) |
|
470 | { |
||
471 | $this->detectDbSettings($output); |
||
472 | $db = $this->getConnection(); |
||
473 | $db->query('DROP DATABASE `' . $this->dbSettings['dbname'] . '`'); |
||
474 | $output->writeln('<info>Dropped database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>'); |
||
475 | } |
||
476 | |||
477 | /** |
||
478 | * @param OutputInterface $output |
||
479 | */ |
||
480 | public function dropTables($output) |
||
493 | |||
494 | /** |
||
495 | * @param OutputInterface $output |
||
496 | */ |
||
497 | View Code Duplication | public function createDatabase($output) |
|
498 | { |
||
499 | $this->detectDbSettings($output); |
||
500 | $db = $this->getConnection(); |
||
501 | $db->query('CREATE DATABASE IF NOT EXISTS `' . $this->dbSettings['dbname'] . '`'); |
||
502 | $output->writeln('<info>Created database</info> <comment>' . $this->dbSettings['dbname'] . '</comment>'); |
||
503 | } |
||
504 | |||
505 | /** |
||
506 | * @param string $command example: 'VARIABLES', 'STATUS' |
||
507 | * @param string|null $variable [optional] |
||
508 | * |
||
509 | * @return array |
||
510 | */ |
||
511 | private function runShowCommand($command, $variable = null) |
||
512 | { |
||
513 | $db = $this->getConnection(); |
||
514 | |||
515 | if (null !== $variable) { |
||
516 | $statement = $db->prepare( |
||
517 | 'SHOW /*!50000 GLOBAL */ ' . $command . ' LIKE :like', |
||
518 | array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY) |
||
519 | ); |
||
520 | $statement->execute( |
||
521 | array(':like' => $variable) |
||
522 | ); |
||
523 | } else { |
||
524 | $statement = $db->query('SHOW /*!50000 GLOBAL */ ' . $command); |
||
525 | } |
||
526 | |||
527 | if ($statement) { |
||
528 | /** @var array|string[] $result */ |
||
529 | $result = $statement->fetchAll(PDO::FETCH_ASSOC); |
||
530 | $return = array(); |
||
531 | foreach ($result as $row) { |
||
532 | $return[$row['Variable_name']] = $row['Value']; |
||
533 | } |
||
534 | |||
535 | return $return; |
||
536 | } |
||
537 | |||
538 | return array(); |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * @param string|null $variable [optional] |
||
543 | * |
||
544 | * @return array |
||
545 | */ |
||
546 | public function getGlobalVariables($variable = null) |
||
547 | { |
||
548 | return $this->runShowCommand('VARIABLES', $variable); |
||
549 | } |
||
550 | |||
551 | /** |
||
552 | * @param string|null $variable [optional] |
||
553 | * |
||
554 | * @return array |
||
555 | */ |
||
556 | public function getGlobalStatus($variable = null) |
||
557 | { |
||
558 | return $this->runShowCommand('STATUS', $variable); |
||
559 | } |
||
560 | |||
561 | /** |
||
562 | * @return Application|BaseApplication |
||
563 | */ |
||
564 | private function getApplication() |
||
565 | { |
||
566 | $command = $this->getHelperSet()->getCommand(); |
||
567 | |||
568 | if ($command) { |
||
569 | $application = $command->getApplication(); |
||
570 | } else { |
||
571 | $application = new Application(); |
||
572 | } |
||
573 | |||
574 | return $application; |
||
575 | } |
||
576 | |||
577 | /** |
||
578 | * small helper method to obtain an object of type OutputInterface |
||
579 | * |
||
580 | * @param OutputInterface|null $output |
||
581 | * |
||
582 | * @return OutputInterface |
||
583 | */ |
||
584 | private function fallbackOutput(OutputInterface $output = null) |
||
602 | } |
||
603 |
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.