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 AbstractSqlTranslator 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 AbstractSqlTranslator, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 15 | abstract class AbstractSqlTranslator implements Translator { |
||
| 16 | |||
| 17 | /** |
||
| 18 | * @var array Filter comparison operators |
||
| 19 | */ |
||
| 20 | protected $operators = array('>=', '<=', '>', '<', '=', '!=', '<>', 'in', 'not in', 'is', 'is not', 'like', 'not like'); |
||
| 21 | |||
| 22 | /** |
||
| 23 | * Concatenates the given set of strings that aren't empty. |
||
| 24 | * |
||
| 25 | * Runs implode() after filtering out empty elements. |
||
| 26 | * |
||
| 27 | * Delimiter defaults to a single whitespace character. |
||
| 28 | * |
||
| 29 | * @param array $strings |
||
| 30 | * @param string $delimiter [optional] |
||
| 31 | * @return string |
||
| 32 | */ |
||
| 33 | protected static function concatenate($strings, $delimiter = ' ') { |
||
| 40 | |||
| 41 | /** |
||
| 42 | * Determine whether the given limit and offset will make a difference to |
||
| 43 | * a statement. |
||
| 44 | * |
||
| 45 | * Simply determines whether both are non-zero integers. |
||
| 46 | * |
||
| 47 | * @param int $limit |
||
| 48 | * @param int $offset |
||
| 49 | * @return bool |
||
| 50 | */ |
||
| 51 | protected static function limitIsUseful($limit, $offset) { |
||
| 54 | |||
| 55 | /** |
||
| 56 | * Translate the given storage query into an SQL query. |
||
| 57 | * |
||
| 58 | * @param Storage\Query $storageQuery |
||
| 59 | * @return Database\Query |
||
| 60 | * @throws Exception |
||
| 61 | */ |
||
| 62 | public function translate(Storage\Query $storageQuery) { |
||
| 75 | |||
| 76 | /** |
||
| 77 | * Translate a query that creates a record. |
||
| 78 | * |
||
| 79 | * @param Storage\Query $storageQuery |
||
| 80 | * @return Database\Query |
||
| 81 | */ |
||
| 82 | protected function translateCreate(Storage\Query $storageQuery) { |
||
| 88 | |||
| 89 | /** |
||
| 90 | * Translate a query that reads records. |
||
| 91 | * |
||
| 92 | * @param Storage\Query $storageQuery |
||
| 93 | * @return Database\Query |
||
| 94 | */ |
||
| 95 | protected function translateRead(Storage\Query $storageQuery) { |
||
| 107 | |||
| 108 | /** |
||
| 109 | * Translate a query that updates records. |
||
| 110 | * |
||
| 111 | * @param Storage\Query $storageQuery |
||
| 112 | * @return Database\Query |
||
| 113 | */ |
||
| 114 | View Code Duplication | protected function translateUpdate(Storage\Query $storageQuery) { |
|
| 123 | |||
| 124 | /** |
||
| 125 | * Translate a query that deletes records. |
||
| 126 | * |
||
| 127 | * @param Storage\Query $storageQuery |
||
| 128 | * @return Database\Query |
||
| 129 | */ |
||
| 130 | View Code Duplication | protected function translateDelete(Storage\Query $storageQuery) { |
|
| 139 | |||
| 140 | /** |
||
| 141 | * Escape the given identifier. |
||
| 142 | * |
||
| 143 | * If the value is an array, it is recursively escaped. |
||
| 144 | * |
||
| 145 | * If the value is not a string, it is returned unmodified. |
||
| 146 | * |
||
| 147 | * @param mixed $identifier |
||
| 148 | * @return mixed |
||
| 149 | */ |
||
| 150 | abstract protected function identifier($identifier); |
||
| 151 | |||
| 152 | /** |
||
| 153 | * Prepare the given value. |
||
| 154 | * |
||
| 155 | * If the value is an array, it is recursively prepared. |
||
| 156 | * |
||
| 157 | * @param array|string $value |
||
| 158 | * @return array|string |
||
| 159 | */ |
||
| 160 | protected function value($value) { |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Resolve a placeholder or constant for the given parameter value. |
||
| 170 | * |
||
| 171 | * @param mixed $value |
||
| 172 | * @return string |
||
| 173 | */ |
||
| 174 | protected function resolve($value) { |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Determine whether the given value resolves a placeholder. |
||
| 188 | * |
||
| 189 | * @param mixed $value |
||
| 190 | * @return bool |
||
| 191 | */ |
||
| 192 | protected function resolvesPlaceholder($value) { |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Prepare the given columns as a string. |
||
| 198 | * |
||
| 199 | * @param array|string $columns |
||
| 200 | * @return string |
||
| 201 | */ |
||
| 202 | protected function prepareColumns($columns) { |
||
| 211 | |||
| 212 | /** |
||
| 213 | * Prepare a default operator for the given value. |
||
| 214 | * |
||
| 215 | * @param string $operator |
||
| 216 | * @param mixed $value |
||
| 217 | * @return string |
||
| 218 | */ |
||
| 219 | protected function prepareOperator($operator, $value) { |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Prepare an individual filter condition. |
||
| 247 | * |
||
| 248 | * @param string $column |
||
| 249 | * @param mixed $value |
||
| 250 | * @return string |
||
| 251 | */ |
||
| 252 | protected function prepareFilter($column, $value) { |
||
| 265 | |||
| 266 | /** |
||
| 267 | * Prepare a WHERE clause using the given filter and comparison operator. |
||
| 268 | * |
||
| 269 | * Example filter key-values and their SQL equivalents: |
||
| 270 | * 'id' => 1, // id = '1' |
||
| 271 | * 'name like' => 'Chris', // name LIKE 'Chris' |
||
| 272 | * 'count >' => 10, // count > '10' |
||
| 273 | * 'type in' => [1, 2], // type IN (1, 2) |
||
| 274 | * 'type' => [3, 4] // type IN (3, 4) |
||
| 275 | * |
||
| 276 | * Comparison operator between conditions defaults to 'AND'. |
||
| 277 | * |
||
| 278 | * @param array $filter |
||
| 279 | * @param string $comparison [optional] |
||
| 280 | * @param bool $excludeWhere [optional] |
||
| 281 | * @return string |
||
| 282 | */ |
||
| 283 | protected function prepareWhere(array $filter, $comparison = 'AND', $excludeWhere = false) { |
||
| 303 | |||
| 304 | /** |
||
| 305 | * Prepare an individual order condition. |
||
| 306 | * |
||
| 307 | * @param string $column |
||
| 308 | * @param string $direction [optional] |
||
| 309 | * @return string |
||
| 310 | */ |
||
| 311 | protected function prepareOrder($column, $direction = null) { |
||
| 317 | |||
| 318 | /** |
||
| 319 | * Prepare an ORDER BY clause using the given order. |
||
| 320 | * |
||
| 321 | * Example order key-values: |
||
| 322 | * 'column', |
||
| 323 | * 'other_column' => 'ASC', |
||
| 324 | * 'another_column' => 'DESC |
||
| 325 | * |
||
| 326 | * Ordered ascending by default. |
||
| 327 | * |
||
| 328 | * @param array|string $order |
||
| 329 | * @return string |
||
| 330 | */ |
||
| 331 | protected function prepareOrderBy($order) { |
||
| 344 | |||
| 345 | /** |
||
| 346 | * Prepare a SELECT statement using the given columns, table, clauses and |
||
| 347 | * options. |
||
| 348 | * |
||
| 349 | * @param string $table |
||
| 350 | * @param array|string $columns |
||
| 351 | * @param string $where [optional] |
||
| 352 | * @param string $order [optional] |
||
| 353 | * @param string $limit [optional] |
||
| 354 | * @param bool $distinct [optional] |
||
| 355 | * @return string |
||
| 356 | */ |
||
| 357 | abstract protected function prepareSelect($table, $columns, $where = null, $order = null, $limit = null, $distinct = false); |
||
| 358 | |||
| 359 | /** |
||
| 360 | * Prepare an INSERT INTO statement using the given table and data. |
||
| 361 | * |
||
| 362 | * @param string $table |
||
| 363 | * @param array $data |
||
| 364 | * @return string |
||
| 365 | */ |
||
| 366 | protected function prepareInsert($table, array $data) { |
||
| 377 | |||
| 378 | /** |
||
| 379 | * Prepare an UPDATE statement with the given table, data and clauses. |
||
| 380 | * |
||
| 381 | * @param string $table |
||
| 382 | * @param array $data |
||
| 383 | * @param string $where [optional] |
||
| 384 | * @param string $limit [optional] |
||
| 385 | * @return string |
||
| 386 | */ |
||
| 387 | abstract protected function prepareUpdate($table, $data, $where = null, $limit = null); |
||
| 388 | |||
| 389 | /** |
||
| 390 | * Prepare a DELETE statement with the given table and clauses. |
||
| 391 | * |
||
| 392 | * @param string $table |
||
| 393 | * @param string $where [optional] |
||
| 394 | * @param string $limit [optional] |
||
| 395 | * @return string |
||
| 396 | */ |
||
| 397 | abstract protected function prepareDelete($table, $where = null, $limit = null); |
||
| 398 | |||
| 399 | /** |
||
| 400 | * Prepare the given filter as an array of prepared query parameters. |
||
| 401 | * |
||
| 402 | * @return array |
||
| 403 | */ |
||
| 404 | protected function filterParameters($filter) { |
||
| 427 | |||
| 428 | /** |
||
| 429 | * Retrieve an array of parameters from the given query for executing a |
||
| 430 | * prepared query. |
||
| 431 | * |
||
| 432 | * @param Storage\Query $query |
||
| 433 | * @return array |
||
| 434 | */ |
||
| 435 | public function parameters(Storage\Query $query) { |
||
| 448 | |||
| 449 | } |
||
| 450 |
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.