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 PDODriver 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 PDODriver, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | abstract class PDODriver extends Component implements LoggerAwareInterface |
||
30 | { |
||
31 | use LoggerTrait, BenchmarkTrait; |
||
32 | |||
33 | /** |
||
34 | * One of DatabaseInterface types, must be set on implementation. |
||
35 | */ |
||
36 | const TYPE = null; |
||
37 | |||
38 | /** |
||
39 | * DateTime format to be used to perform automatic conversion of DateTime objects. |
||
40 | * |
||
41 | * @var string |
||
42 | */ |
||
43 | const DATETIME = 'Y-m-d H:i:s'; |
||
44 | |||
45 | /** |
||
46 | * Query result class. |
||
47 | */ |
||
48 | const QUERY_RESULT = PDOQuery::class; |
||
49 | |||
50 | /** |
||
51 | * Driver name. |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | private $name = ''; |
||
56 | |||
57 | /** |
||
58 | * Transaction level (count of nested transactions). Not all drives can support nested |
||
59 | * transactions. |
||
60 | * |
||
61 | * @var int |
||
62 | */ |
||
63 | private $transactionLevel = 0; |
||
64 | |||
65 | /** |
||
66 | * @var PDO|null |
||
67 | */ |
||
68 | private $pdo = null; |
||
69 | |||
70 | /** |
||
71 | * Connection configuration described in DBAL config file. Any driver can be used as data source |
||
72 | * for multiple databases as table prefix and quotation defined on Database instance level. |
||
73 | * |
||
74 | * @var array |
||
75 | */ |
||
76 | protected $config = [ |
||
77 | 'profiling' => false, |
||
78 | |||
79 | //DSN |
||
80 | 'connection' => '', |
||
81 | 'username' => '', |
||
82 | 'password' => '', |
||
83 | 'options' => [], |
||
84 | ]; |
||
85 | |||
86 | /** |
||
87 | * PDO connection options set. |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | protected $options = [ |
||
92 | PDO::ATTR_CASE => PDO::CASE_NATURAL, |
||
93 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
||
94 | PDO::ATTR_STRINGIFY_FETCHES => true, |
||
95 | ]; |
||
96 | |||
97 | /** |
||
98 | * @param string $name |
||
99 | * @param array $connection |
||
100 | * @throws SugarException |
||
101 | */ |
||
102 | public function __construct(string $name, array $connection) |
||
111 | |||
112 | /** |
||
113 | * Source name, can include database name or database file. |
||
114 | * |
||
115 | * @return string |
||
116 | */ |
||
117 | public function getName(): string |
||
121 | |||
122 | /** |
||
123 | * Get driver source database or file name. |
||
124 | * |
||
125 | * @return string |
||
126 | * |
||
127 | * @throws DriverException |
||
128 | */ |
||
129 | public function getSource(): string |
||
137 | |||
138 | /** |
||
139 | * Database type driver linked to. |
||
140 | * |
||
141 | * @return string |
||
142 | */ |
||
143 | public function getType(): string |
||
147 | |||
148 | /** |
||
149 | * Connection specific timezone, at this moment locked to UTC. |
||
150 | * |
||
151 | * @todo Support connection specific timezones. |
||
152 | * |
||
153 | * @return \DateTimeZone |
||
154 | */ |
||
155 | public function getTimezone(): \DateTimeZone |
||
159 | |||
160 | /** |
||
161 | * Enabled profiling will raise set of log messages and benchmarks associated with PDO queries. |
||
162 | * |
||
163 | * @param bool $enabled Enable or disable driver profiling. |
||
164 | * |
||
165 | * @return self |
||
166 | */ |
||
167 | public function setProfiling(bool $enabled = true): PDODriver |
||
173 | |||
174 | /** |
||
175 | * Check if profiling mode is enabled. |
||
176 | * |
||
177 | * @return bool |
||
178 | */ |
||
179 | public function isProfiling(): bool |
||
183 | |||
184 | /** |
||
185 | * Force driver to connect. |
||
186 | * |
||
187 | * @return PDO |
||
188 | * |
||
189 | * @throws DriverException |
||
190 | */ |
||
191 | public function connect(): PDO |
||
206 | |||
207 | /** |
||
208 | * Disconnect driver. |
||
209 | * |
||
210 | * @return self |
||
211 | */ |
||
212 | public function disconnect(): PDODriver |
||
218 | |||
219 | /** |
||
220 | * Check if driver already connected. |
||
221 | * |
||
222 | * @return bool |
||
223 | */ |
||
224 | public function isConnected(): bool |
||
228 | |||
229 | /** |
||
230 | * Change PDO instance associated with driver. |
||
231 | * |
||
232 | * @param PDO $pdo |
||
233 | * |
||
234 | * @return self |
||
235 | */ |
||
236 | public function setPDO(PDO $pdo): PDODriver |
||
242 | |||
243 | /** |
||
244 | * Get associated PDO connection. Will automatically connect if such connection does not exists. |
||
245 | * |
||
246 | * @return PDO |
||
247 | */ |
||
248 | public function getPDO(): PDO |
||
256 | |||
257 | /** |
||
258 | * Driver specific database/table identifier quotation. |
||
259 | * |
||
260 | * @param string $identifier |
||
261 | * |
||
262 | * @return string |
||
263 | */ |
||
264 | public function identifier(string $identifier): string |
||
268 | |||
269 | /** |
||
270 | * Wraps PDO query method with custom representation class. |
||
271 | * |
||
272 | * @param string $statement |
||
273 | * @param array $parameters |
||
274 | * @param string $class Class name to be used, by default driver specific query result to be |
||
275 | * used. |
||
276 | * @param array $args Class construction arguments (by default filtered parameters). |
||
277 | * |
||
278 | * @return \PDOStatement|PDOQuery |
||
279 | */ |
||
280 | public function query( |
||
288 | |||
289 | /** |
||
290 | * Create instance of PDOStatement using provided SQL query and set of parameters and execute |
||
291 | * it. |
||
292 | * |
||
293 | * @param string $query |
||
294 | * @param array $parameters Parameters to be binded into query. |
||
295 | * @param string $class Class to represent PDO statement. |
||
296 | * @param array $args Class construction arguments (by default filtered parameters) |
||
297 | * |
||
298 | * @return \PDOStatement |
||
299 | * |
||
300 | * @throws QueryException |
||
301 | */ |
||
302 | public function statement( |
||
350 | |||
351 | /** |
||
352 | * Get prepared PDO statement. |
||
353 | * |
||
354 | * @param string $statement Query statement. |
||
355 | * @param string $class Class to represent PDO statement. |
||
356 | * @param array $args Class construction arguments (by default paramaters) |
||
357 | * |
||
358 | * @return \PDOStatement |
||
359 | */ |
||
360 | public function prepare( |
||
370 | |||
371 | /** |
||
372 | * Get id of last inserted row, this method must be called after insert query. Attention, |
||
373 | * such functionality may not work in some DBMS property (Postgres). |
||
374 | * |
||
375 | * @param string|null $sequence Name of the sequence object from which the ID should be |
||
376 | * returned. |
||
377 | * |
||
378 | * @return mixed |
||
379 | */ |
||
380 | public function lastInsertID(string $sequence = null) |
||
386 | |||
387 | /** |
||
388 | * Prepare set of query builder/user parameters to be send to PDO. Must convert DateTime |
||
389 | * instances into valid database timestamps and resolve values of ParameterInterface. |
||
390 | * |
||
391 | * Every value has to wrapped with parameter interface. |
||
392 | * |
||
393 | * @param array $parameters |
||
394 | * |
||
395 | * @return ParameterInterface[] |
||
396 | * |
||
397 | * @throws DriverException |
||
398 | */ |
||
399 | public function flattenParameters(array $parameters): array |
||
453 | |||
454 | /** |
||
455 | * Start SQL transaction with specified isolation level (not all DBMS support it). Nested |
||
456 | * transactions are processed using savepoints. |
||
457 | * |
||
458 | * @link http://en.wikipedia.org/wiki/Database_transaction |
||
459 | * @link http://en.wikipedia.org/wiki/Isolation_(database_systems) |
||
460 | * |
||
461 | * @param string $isolationLevel |
||
462 | * |
||
463 | * @return bool |
||
464 | */ |
||
465 | public function beginTransaction(string $isolationLevel = null): bool |
||
485 | |||
486 | /** |
||
487 | * Commit the active database transaction. |
||
488 | * |
||
489 | * @return bool |
||
490 | */ |
||
491 | View Code Duplication | public function commitTransaction(): bool |
|
507 | |||
508 | /** |
||
509 | * Rollback the active database transaction. |
||
510 | * |
||
511 | * @return bool |
||
512 | */ |
||
513 | View Code Duplication | public function rollbackTransaction(): bool |
|
529 | |||
530 | /** |
||
531 | * @return array |
||
532 | */ |
||
533 | public function __debugInfo() |
||
543 | |||
544 | /** |
||
545 | * Create instance of configured PDO class. |
||
546 | * |
||
547 | * @return PDO |
||
548 | */ |
||
549 | protected function createPDO(): PDO |
||
558 | |||
559 | /** |
||
560 | * Convert PDO exception into query or integrity exception. |
||
561 | * |
||
562 | * @param \PDOException $exception |
||
563 | * |
||
564 | * @return QueryException |
||
565 | */ |
||
566 | protected function clarifyException(\PDOException $exception): QueryException |
||
571 | |||
572 | /** |
||
573 | * Set transaction isolation level, this feature may not be supported by specific database |
||
574 | * driver. |
||
575 | * |
||
576 | * @param string $level |
||
577 | */ |
||
578 | protected function isolationLevel(string $level) |
||
588 | |||
589 | /** |
||
590 | * Create nested transaction save point. |
||
591 | * |
||
592 | * @link http://en.wikipedia.org/wiki/Savepoint |
||
593 | * |
||
594 | * @param string $name Savepoint name/id, must not contain spaces and be valid database |
||
595 | * identifier. |
||
596 | */ |
||
597 | View Code Duplication | protected function savepointCreate(string $name) |
|
605 | |||
606 | /** |
||
607 | * Commit/release savepoint. |
||
608 | * |
||
609 | * @link http://en.wikipedia.org/wiki/Savepoint |
||
610 | * |
||
611 | * @param string $name Savepoint name/id, must not contain spaces and be valid database |
||
612 | * identifier. |
||
613 | */ |
||
614 | View Code Duplication | protected function savepointRelease(string $name) |
|
622 | |||
623 | /** |
||
624 | * Rollback savepoint. |
||
625 | * |
||
626 | * @link http://en.wikipedia.org/wiki/Savepoint |
||
627 | * |
||
628 | * @param string $name Savepoint name/id, must not contain spaces and be valid database |
||
629 | * identifier. |
||
630 | */ |
||
631 | View Code Duplication | protected function savepointRollback(string $name) |
|
638 | |||
639 | /** |
||
640 | * Convert DateTime object into local database representation. Driver will automatically force |
||
641 | * needed timezone. |
||
642 | * |
||
643 | * @param \DateTime $dateTime |
||
644 | * |
||
645 | * @return string |
||
646 | */ |
||
647 | protected function resolveDateTime(\DateTime $dateTime): string |
||
651 | |||
652 | /** |
||
653 | * Bind parameters into statement. |
||
654 | * |
||
655 | * @param \PDOStatement $statement |
||
656 | * @param ParameterInterface[] $parameters Named hash of ParameterInterface. |
||
657 | * @return \PDOStatement |
||
658 | */ |
||
659 | private function bindParameters(\PDOStatement $statement, array $parameters): \PDOStatement |
||
673 | } |
||
674 |
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.