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 SqliteDb 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 SqliteDb, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class SqliteDb extends MySqlDb { |
||
16 | /** |
||
17 | * {@inheritdoc} |
||
18 | */ |
||
19 | 4 | protected function alterTable($tablename, array $alterdef, array $options = []) { |
|
22 | |||
23 | /** |
||
24 | * Alter a table by creating a new table and copying the old table's data to it. |
||
25 | * |
||
26 | * @param string $tablename The table to alter. |
||
27 | * @param array $alterDef The new definition. |
||
28 | * @param array $options An array of options for the migration. |
||
29 | */ |
||
30 | 4 | protected function alterTableMigrate($tablename, array $alterDef, array $options = []) { |
|
31 | 4 | $currentDef = $this->getTableDef($tablename); |
|
32 | |||
33 | // Merge the table definitions if we aren't dropping stuff. |
||
34 | 4 | if (!val(Db::OPTION_DROP, $options)) { |
|
35 | 3 | $tableDef = $this->mergeTableDefs($currentDef, $alterDef); |
|
36 | } else { |
||
37 | 1 | $tableDef = $alterDef['def']; |
|
38 | } |
||
39 | |||
40 | // Drop all of the indexes on the current table. |
||
41 | 4 | foreach (val('indexes', $currentDef, []) as $indexDef) { |
|
42 | 4 | if (val('type', $indexDef, Db::INDEX_IX) === Db::INDEX_IX) { |
|
43 | 4 | $this->dropIndex($indexDef['name']); |
|
44 | } |
||
45 | } |
||
46 | |||
47 | 4 | $tmpTablename = $tablename.'_'.time(); |
|
48 | |||
49 | // Rename the current table. |
||
50 | 4 | $this->renameTable($tablename, $tmpTablename); |
|
51 | |||
52 | // Create the new table. |
||
53 | 4 | $this->createTable($tablename, $tableDef, $options); |
|
54 | |||
55 | // Figure out the columns that we can insert. |
||
56 | 4 | $columns = array_keys(array_intersect_key($tableDef['columns'], $currentDef['columns'])); |
|
57 | |||
58 | // Build the insert/select statement. |
||
59 | 4 | $sql = 'insert into '.$this->backtick($this->px.$tablename)."\n". |
|
60 | 4 | $this->bracketList($columns, '`')."\n". |
|
61 | 4 | $this->buildSelect($tmpTablename, [], ['columns' => $columns]); |
|
62 | |||
63 | 4 | $this->query($sql, Db::QUERY_WRITE); |
|
64 | |||
65 | // Drop the temp table. |
||
66 | 4 | $this->dropTable($tmpTablename); |
|
67 | 4 | } |
|
68 | |||
69 | /** |
||
70 | * Rename a table. |
||
71 | * |
||
72 | * @param string $oldname The old name of the table. |
||
73 | * @param string $newname The new name of the table. |
||
74 | */ |
||
75 | 4 | protected function renameTable($oldname, $newname) { |
|
82 | |||
83 | /** |
||
84 | * Merge a table def with its alter def so that no columns/indexes are lost in an alter. |
||
85 | * |
||
86 | * @param array $tableDef The table def. |
||
87 | * @param array $alterDef The alter def. |
||
88 | * @return array The new table def. |
||
89 | */ |
||
90 | 3 | protected function mergeTableDefs(array $tableDef, array $alterDef) { |
|
98 | |||
99 | /** |
||
100 | * Drop an index. |
||
101 | * |
||
102 | * @param string $indexName The name of the index to drop. |
||
103 | */ |
||
104 | 2 | protected function dropIndex($indexName) { |
|
109 | |||
110 | /** |
||
111 | * {@inheritdoc} |
||
112 | */ |
||
113 | 24 | protected function buildInsert($tablename, array $row, $quotevals = true, $options = []) { |
|
114 | 24 | if (val(Db::OPTION_UPSERT, $options)) { |
|
115 | throw new \Exception("Upsert is not supported."); |
||
116 | 24 | } elseif (val(Db::OPTION_IGNORE, $options)) { |
|
117 | 2 | $sql = 'insert or ignore into '; |
|
118 | 23 | } elseif (val(Db::OPTION_REPLACE, $options)) { |
|
119 | 2 | $sql = 'insert or replace into '; |
|
120 | } else { |
||
121 | 22 | $sql = 'insert into '; |
|
122 | } |
||
123 | 24 | $sql .= $this->backtick($this->px.$tablename); |
|
124 | |||
125 | // Add the list of values. |
||
126 | $sql .= |
||
127 | 24 | "\n".$this->bracketList(array_keys($row), '`'). |
|
128 | 24 | "\nvalues".$this->bracketList($row, $quotevals ? "'" : ''); |
|
129 | |||
130 | 24 | return $sql; |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * {@inheritdoc} |
||
135 | */ |
||
136 | 1 | protected function buildLike($column, $value, $quotevals) { |
|
139 | |||
140 | /** |
||
141 | * {@inheritdoc} |
||
142 | */ |
||
143 | 4 | View Code Duplication | protected function buildUpdate($tablename, array $set, array $where, $quotevals = true, array $options = []) { |
|
|||
144 | $sql = 'update '. |
||
145 | 4 | (val(Db::OPTION_IGNORE, $options) ? 'or ignore ' : ''). |
|
146 | 4 | $this->backtick($this->px.$tablename). |
|
147 | 4 | "\nset\n "; |
|
148 | |||
149 | 4 | $parts = []; |
|
150 | 4 | foreach ($set as $key => $value) { |
|
151 | 4 | $parts[] = $this->backtick($key).' = '.$this->quoteVal($value, $quotevals); |
|
152 | } |
||
153 | 4 | $sql .= implode(",\n ", $parts); |
|
154 | |||
155 | 4 | if (!empty($where)) { |
|
156 | 4 | $sql .= "\nwhere ".$this->buildWhere($where, Db::OP_AND, $quotevals); |
|
157 | } |
||
158 | |||
159 | 4 | return $sql; |
|
160 | } |
||
161 | |||
162 | /** |
||
163 | * Construct a column definition string from an array defintion. |
||
164 | * |
||
165 | * @param string $name The name of the column. |
||
166 | * @param array $def The column definition. |
||
167 | * @return string Returns a string representing the column definition. |
||
168 | */ |
||
169 | 8 | protected function columnDefString($name, array $def) { |
|
170 | // Auto-increments MUST be of type integer. |
||
171 | 8 | if (val('autoincrement', $def)) { |
|
172 | 2 | $def['type'] = 'integer'; |
|
173 | } |
||
174 | |||
175 | 8 | $result = $this->backtick($name).' '.$this->columnTypeString($def['type']); |
|
176 | |||
177 | 8 | if (val('primary', $def) && val('autoincrement', $def)) { |
|
178 | // if (val('autoincrement', $def)) { |
||
179 | 2 | $result .= ' primary key autoincrement'; |
|
180 | 2 | $def['primary'] = true; |
|
181 | // } |
||
182 | 8 | } elseif (isset($def['default'])) { |
|
183 | 5 | $result .= ' default '.$this->quoteVal($def['default']); |
|
184 | 8 | } elseif (val('required', $def)) { |
|
185 | 7 | $result .= ' not null'; |
|
186 | } |
||
187 | |||
188 | 8 | return $result; |
|
189 | } |
||
190 | |||
191 | /** |
||
192 | * {@inheritdoc} |
||
193 | */ |
||
194 | 8 | protected function createTable($tablename, array $tabledef, array $options = []) { |
|
195 | 8 | $parts = []; |
|
196 | |||
197 | // Make sure the primary key columns are defined first and in order. |
||
198 | 8 | $autoinc = false; |
|
199 | 8 | if (isset($tabledef['indexes']['primary'])) { |
|
200 | 5 | $pkIndex = $tabledef['indexes']['primary']; |
|
201 | 5 | foreach ($pkIndex['columns'] as $column) { |
|
202 | 5 | $cdef = $tabledef['columns'][$column]; |
|
203 | 5 | $parts[] = $this->columnDefString($column, $cdef); |
|
204 | 5 | $autoinc |= val('autoincrement', $cdef, false); |
|
205 | 5 | unset($tabledef['columns'][$column]); |
|
206 | } |
||
207 | } |
||
208 | |||
209 | 8 | foreach ($tabledef['columns'] as $name => $cdef) { |
|
210 | 7 | $parts[] = $this->columnDefString($name, $cdef); |
|
211 | } |
||
212 | |||
213 | // Add the prinary key index. |
||
214 | 8 | if (isset($pkIndex) && !$autoinc) { |
|
215 | 3 | $parts[] = 'primary key '.$this->bracketList($pkIndex['columns'], '`'); |
|
216 | } |
||
217 | |||
218 | 8 | $fullTablename = $this->backtick($this->px.$tablename); |
|
219 | 8 | $sql = "create table $fullTablename (\n ". |
|
220 | 8 | implode(",\n ", $parts). |
|
221 | 8 | "\n)"; |
|
222 | |||
223 | 8 | $this->query($sql, Db::QUERY_DEFINE); |
|
224 | |||
225 | // Add the rest of the indexes. |
||
226 | 8 | foreach (val('indexes', $tabledef, []) as $index) { |
|
227 | 8 | if (val('type', $index, Db::INDEX_IX) !== Db::INDEX_PK) { |
|
228 | 8 | $this->createIndex($tablename, $index, $options); |
|
229 | } |
||
230 | } |
||
231 | 8 | } |
|
232 | |||
233 | /** |
||
234 | * Create an index. |
||
235 | * |
||
236 | * @param string $tablename The name of the table to create the index on. |
||
237 | * @param array $indexDef The index definition. |
||
238 | * @param array $options Additional options for the index creation. |
||
239 | */ |
||
240 | 5 | public function createIndex($tablename, array $indexDef, $options = []) { |
|
252 | |||
253 | /** |
||
254 | * Force a value into the appropriate php type based on its Sqlite type. |
||
255 | * |
||
256 | * @param mixed $value The value to force. |
||
257 | * @param string $type The sqlite type name. |
||
258 | * @return mixed Returns $value cast to the appropriate type. |
||
259 | */ |
||
260 | View Code Duplication | protected function forceType($value, $type) { |
|
275 | |||
276 | /** |
||
277 | * Get the columns for tables and put them in {MySqlDb::$tables}. |
||
278 | * |
||
279 | * @param string $tablename The table to get the columns for or blank for all columns. |
||
280 | * @return array|null Returns an array of columns if {@link $tablename} is specified, or null otherwise. |
||
281 | */ |
||
282 | 5 | protected function getColumns($tablename = '') { |
|
325 | |||
326 | /** |
||
327 | * Get the indexes from the database. |
||
328 | * |
||
329 | * @param string $tablename The name of the table to get the indexes for or an empty string to get all indexes. |
||
330 | * @return array|null |
||
331 | */ |
||
332 | 5 | protected function getIndexes($tablename = '') { |
|
371 | |||
372 | /** |
||
373 | * Get the primary or secondary keys from the given rows. |
||
374 | * |
||
375 | * @param string $tablename The name of the table. |
||
376 | * @param array $row The row to examine. |
||
377 | * @param bool $quick Whether or not to quickly look for <tablename>ID for the primary key. |
||
378 | * @return array|null Returns the primary keys and values from {@link $rows} or null if the primary key isn't found. |
||
379 | */ |
||
380 | 2 | protected function getPKValue($tablename, array $row, $quick = false) { |
|
396 | |||
397 | /** |
||
398 | * Get the all of tablenames in the database. |
||
399 | * |
||
400 | * @return array Returns an array of table names with prefixes stripped. |
||
401 | */ |
||
402 | 1 | View Code Duplication | protected function getTablenames() { |
423 | |||
424 | /** |
||
425 | * {@inheritdoc} |
||
426 | */ |
||
427 | 7 | public function insert($tablename, array $rows, array $options = []) { |
|
456 | |||
457 | /** |
||
458 | * Gets the {@link PDO} object for this connection. |
||
459 | * |
||
460 | * @return \PDO |
||
461 | */ |
||
462 | 31 | public function pdo() { |
|
470 | |||
471 | /** |
||
472 | * Optionally quote a where value. |
||
473 | * |
||
474 | * @param mixed $value The value to quote. |
||
475 | * @param bool $quote Whether or not to quote the value. |
||
476 | * @return string Returns the value, optionally quoted. |
||
477 | */ |
||
478 | 21 | public function quoteVal($value, $quote = true) { |
|
494 | } |
||
495 |
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.