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 BaseConnection 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 BaseConnection, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | abstract class BaseConnection implements DatabaseConnection, ProfilerAware, Dumpable { |
||
29 | |||
30 | use ProfilerAwareTrait; |
||
31 | |||
32 | /** |
||
33 | * Connection details. |
||
34 | * @var DSN |
||
35 | */ |
||
36 | protected $dsn = null; |
||
37 | |||
38 | /** |
||
39 | * Underlying PDO object. |
||
40 | * @var \PDO |
||
41 | */ |
||
42 | protected $pdo = null; |
||
43 | |||
44 | /** |
||
45 | * Prepared statement cache. |
||
46 | * @var array |
||
47 | */ |
||
48 | protected $statements = []; |
||
49 | |||
50 | /** |
||
51 | * Create a new database connection. |
||
52 | * |
||
53 | * @param DSN $dsn a DSN instance describing the database connection details |
||
54 | */ |
||
55 | public function __construct( DSN $dsn ) { |
||
68 | |||
69 | public function connect() { |
||
99 | |||
100 | public function disconnect() { |
||
104 | |||
105 | public function isConnected() { |
||
108 | |||
109 | public function select() { |
||
112 | |||
113 | public function insert() { |
||
116 | |||
117 | public function update() { |
||
120 | |||
121 | public function replace() { |
||
124 | |||
125 | public function delete() { |
||
128 | |||
129 | public function prepare( $statement ) { |
||
147 | |||
148 | public function query( $statement, $params = [] ) { |
||
184 | |||
185 | public function execute( $statement, $params = [] ) { |
||
189 | |||
190 | View Code Duplication | public function getAll( $statement, $params = [], $expires = 0, $key = '' ) { |
|
204 | |||
205 | public function getAssoc( $statement, $params = [], $expires = 0, $key = '' ) { |
||
221 | |||
222 | public function getAssocMulti( $statement, $params = [], $expires = 0, $key = '' ) { |
||
242 | |||
243 | View Code Duplication | public function getRow( $statement, $params = [], $expires = 0, $key = '' ) { |
|
257 | |||
258 | View Code Duplication | public function getCol( $statement, $params = [], $expires = 0, $key = '' ) { |
|
273 | |||
274 | View Code Duplication | public function getOne( $statement, $params = [], $expires = 0, $key = '' ) { |
|
288 | |||
289 | public function begin() { |
||
301 | |||
302 | View Code Duplication | public function commit() { |
|
315 | |||
316 | View Code Duplication | public function rollback() { |
|
329 | |||
330 | public function inTransaction() { |
||
333 | |||
334 | public function insertId( $name = '' ) { |
||
339 | |||
340 | public function quote( $value, $type = \PDO::PARAM_STR ) { |
||
344 | |||
345 | public function quoteIdentifier( $name ) { |
||
362 | |||
363 | /** |
||
364 | * Execute a raw SQL string and return the number of affected rows. |
||
365 | * Primarily used for DDL queries. Do not use this with: |
||
366 | * - Anything (data/parameters/etc) that comes from userland |
||
367 | * - Select queries - the answer will always be 0 as no rows are affected. |
||
368 | * - Everyday queries - use query() or execute() |
||
369 | * @param string $sql the SQL statement to exexcute |
||
370 | * @return integer the number of rows affected by the statement |
||
371 | */ |
||
372 | public function rawExec( $sql ) { |
||
384 | |||
385 | public function dump( $dumper = '\\yolk\\debug\\TextDumper', $depth = 1 ) { |
||
389 | |||
390 | /** |
||
391 | * Bind named and positional parameters to a PDOStatement. |
||
392 | * @param PDOStatement $statement |
||
393 | * @param array $params |
||
394 | * @return void |
||
395 | */ |
||
396 | protected function bindParams( \PDOStatement $statement, array $params ) { |
||
413 | |||
414 | /** |
||
415 | * Perform a select query and use a callback to extract a result. |
||
416 | * @param \PDOStatement|string $statement an existing PDOStatement object or a SQL string. |
||
417 | * @param array $params an array of parameters to pass into the query. |
||
418 | * @param integer $expires number of seconds to cache the result for if caching is enabled |
||
419 | * @param string $key cache key used to store the result |
||
420 | * @param \Closure $callback function to yield a result from the executed statement |
||
421 | * @return array |
||
422 | */ |
||
423 | protected function getResult( $statement, $params, $expires, $key, \Closure $callback ) { |
||
436 | |||
437 | protected function getOption( $option, $default = null ) { |
||
440 | |||
441 | /** |
||
442 | * Make sure the connection is using the correct character set |
||
443 | * |
||
444 | * @param string $charset the character set to use for the connection |
||
445 | * @param string $collation the collation method to use for the connection |
||
446 | * @return self |
||
447 | */ |
||
448 | protected function setCharacterSet( $charset, $collation = '' ) { |
||
463 | |||
464 | } |
||
465 | |||
466 | // EOF |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.