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 QueryStatement 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 QueryStatement, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
49 | class QueryStatement implements QueryStatementInterface |
||
50 | { |
||
51 | /** |
||
52 | * @var SessionInterface |
||
53 | */ |
||
54 | protected $session; |
||
55 | |||
56 | /** |
||
57 | * @var string |
||
58 | */ |
||
59 | protected $statement; |
||
60 | |||
61 | /** |
||
62 | * @var array |
||
63 | */ |
||
64 | protected $parametersMap = []; |
||
65 | |||
66 | /** |
||
67 | * Creates a prepared statement for querying the CMIS repository. Requires |
||
68 | * at least the Session as parameter, then accepts either a manual statement |
||
69 | * or a list of property IDs, type IDs, a where clause and orderings which |
||
70 | * will then generate a prepared statement based on those values. |
||
71 | * |
||
72 | * See also main class desciption. |
||
73 | * |
||
74 | * @param SessionInterface $session The initialized Session for communicating |
||
75 | * @param string $statement Optional, manually prepared statement. If provided, |
||
76 | * excludes the use of property list, type list, where clause and ordering. |
||
77 | * @param array $selectPropertyIds An array PropertyDefinitionInterface |
||
78 | * or strings, can be mixed. When strings are provided those can be |
||
79 | * either the actual ID of the property or the query name thereof. |
||
80 | * @param array $fromTypes An array of TypeDefinitionInterface or strings, |
||
81 | * can be mixed. When strings are provided those can be either the |
||
82 | * actual ID of the type, or it can be the query name thereof. If |
||
83 | * an array of arrays is provided, each array is expected to contain |
||
84 | * a TypeDefinition or string as first member and an alias as second. |
||
85 | * @param string|null $whereClause If searching by custom clause, provide here. |
||
86 | * @param array $orderByPropertyIds List of property IDs by which to sort. |
||
87 | * Each value can be either a PropertyDefinitionInterface instance, |
||
88 | * a string (in which case, ID or queryName) or an array of a string |
||
89 | * or PropertyDefinition as first member and ASC or DESC as second. |
||
90 | * E.g. valid strings: "cm:title ASC", "cm:title", "P:cm:title". |
||
91 | * Valid arrays: [PropertyDefinitionInterface, "ASC"], ["cm:title", "ASC"] |
||
92 | * @throws CmisInvalidArgumentException |
||
93 | */ |
||
94 | 120 | public function __construct( |
|
153 | |||
154 | /** |
||
155 | * Generates a statement based on input criteria, with the necessary |
||
156 | * JOINs in place for selecting attributes related to all provided types. |
||
157 | * |
||
158 | * @param array $selectPropertyIds An array PropertyDefinitionInterface |
||
159 | * or strings, can be mixed. When strings are provided those can be |
||
160 | * either the actual ID of the property or the query name thereof. |
||
161 | * @param array $fromTypes An array of TypeDefinitionInterface or strings, |
||
162 | * can be mixed. When strings are provided those can be either the |
||
163 | * actual ID of the type, or it can be the query name thereof. If |
||
164 | * an array of arrays is provided, each array is expected to contain |
||
165 | * a TypeDefinition or string as first member and an alias as second. |
||
166 | * @param string|null $whereClause If searching by custom clause, provide here. |
||
167 | * @param array $orderByPropertyIds List of property IDs by which to sort. |
||
168 | * Each value can be either a PropertyDefinitionInterface instance, |
||
169 | * a string (in which case, ID or queryName) or an array of a string |
||
170 | * or PropertyDefinition as first member and ASC or DESC as second. |
||
171 | * E.g. valid strings: "cm:title ASC", "cm:title", "P:cm:title". |
||
172 | * Valid arrays: [PropertyDefinitionInterface, "ASC"], ["cm:title", "ASC"] |
||
173 | * @return string |
||
174 | */ |
||
175 | 22 | protected function generateStatementFromPropertiesAndTypesLists( |
|
212 | |||
213 | /** |
||
214 | * Translates a TypeDefinition or string into a query name for |
||
215 | * that TypeDefinition. Returns the input string as fallback if |
||
216 | * the type could not be resolved. Input may contain an alias, |
||
217 | * if so, we split and preserve the alias but attempt to translate |
||
218 | * the type ID part. |
||
219 | * |
||
220 | * @param mixed $typeDefinitionMixed Input describing the type |
||
221 | * @param string $autoAlias If alias is not provided |
||
222 | * @return array |
||
223 | */ |
||
224 | 23 | protected function getQueryNameAndAliasForType($typeDefinitionMixed, $autoAlias) |
|
242 | |||
243 | /** |
||
244 | * Renders a statement-compatible string of property selections, |
||
245 | * with ordering support if $withOrdering is true. Input properties |
||
246 | * can be an array of strings, an array of PropertyDefinition, or |
||
247 | * when $withOrdering is true, an array of arrays each containing |
||
248 | * a string or PropertyDefinition plus ASC or DESC as second value. |
||
249 | * |
||
250 | * @param array $properties |
||
251 | * @param boolean $withOrdering |
||
252 | * @return string |
||
253 | */ |
||
254 | 22 | protected function generateStatementPropertyList(array $properties, $withOrdering) |
|
275 | |||
276 | /** |
||
277 | * Executes the query. |
||
278 | * |
||
279 | * @param boolean $searchAllVersions <code>true</code> if all document versions should be included in the search |
||
280 | * results, <code>false</code> if only the latest document versions should be included in the search results |
||
281 | * @param OperationContextInterface|null $context the operation context to use |
||
282 | * @return QueryResultInterface[] |
||
283 | */ |
||
284 | 1 | public function query($searchAllVersions, OperationContextInterface $context = null) |
|
288 | |||
289 | /** |
||
290 | * Sets the designated parameter to the given boolean. |
||
291 | * |
||
292 | * @param integer $parameterIndex the parameter index (one-based) |
||
293 | * @param boolean $bool the boolean |
||
294 | */ |
||
295 | 3 | public function setBoolean($parameterIndex, $bool) |
|
299 | |||
300 | /** |
||
301 | * Sets the designated parameter to the given DateTime value. |
||
302 | * |
||
303 | * @param integer $parameterIndex the parameter index (one-based) |
||
304 | * @param \DateTime $dateTime the DateTime value as DateTime object |
||
305 | */ |
||
306 | 3 | public function setDateTime($parameterIndex, \DateTime $dateTime) |
|
310 | |||
311 | /** |
||
312 | * Sets the designated parameter to the given DateTime value with the prefix 'TIMESTAMP '. |
||
313 | * |
||
314 | * @param integer $parameterIndex the parameter index (one-based) |
||
315 | * @param \DateTime $dateTime the DateTime value as DateTime object |
||
316 | */ |
||
317 | 3 | public function setDateTimeTimestamp($parameterIndex, \DateTime $dateTime) |
|
321 | |||
322 | /** |
||
323 | * Sets the designated parameter to the given object ID. |
||
324 | * |
||
325 | * @param integer $parameterIndex the parameter index (one-based) |
||
326 | * @param ObjectIdInterface $id the object ID |
||
327 | */ |
||
328 | 6 | public function setId($parameterIndex, ObjectIdInterface $id) |
|
332 | |||
333 | /** |
||
334 | * Sets the designated parameter to the given number. |
||
335 | * |
||
336 | * @param integer $parameterIndex the parameter index (one-based) |
||
337 | * @param integer $number the value to be set as number |
||
338 | * @throws CmisInvalidArgumentException If number not of type integer |
||
339 | */ |
||
340 | 5 | public function setNumber($parameterIndex, $number) |
|
348 | |||
349 | /** |
||
350 | * Sets the designated parameter to the query name of the given property. |
||
351 | * |
||
352 | * @param integer $parameterIndex the parameter index (one-based) |
||
353 | * @param PropertyDefinitionInterface $propertyDefinition |
||
354 | * @throws CmisInvalidArgumentException If property has no query name |
||
355 | */ |
||
356 | 4 | public function setProperty($parameterIndex, PropertyDefinitionInterface $propertyDefinition) |
|
365 | |||
366 | /** |
||
367 | * Sets the designated parameter to the given string. |
||
368 | * |
||
369 | * @param integer $parameterIndex the parameter index (one-based) |
||
370 | * @param string $string the string |
||
371 | * @throws CmisInvalidArgumentException If given value is not a string |
||
372 | */ |
||
373 | 8 | public function setString($parameterIndex, $string) |
|
381 | |||
382 | /** |
||
383 | * Sets the designated parameter to the given string in a CMIS contains statement. |
||
384 | * |
||
385 | * Note that the CMIS specification requires two levels of escaping. The first level escapes ', ", \ characters |
||
386 | * to \', \" and \\. The characters *, ? and - are interpreted as text search operators and are not escaped |
||
387 | * on first level. |
||
388 | * If *, ?, - shall be used as literals, they must be passed escaped with \*, \? and \- to this method. |
||
389 | * |
||
390 | * For all statements in a CONTAINS() clause it is required to isolate those from a query statement. |
||
391 | * Therefore a second level escaping is performed. On the second level grammar ", ', - and \ are escaped with a \. |
||
392 | * See the spec for further details. |
||
393 | * |
||
394 | * Summary (input --> first level escaping --> second level escaping and output): |
||
395 | * * --> * --> * |
||
396 | * ? --> ? --> ? |
||
397 | * - --> - --> - |
||
398 | * \ --> \\ --> \\\\ |
||
399 | * (for any other character following other than * ? -) |
||
400 | * \* --> \* --> \\* |
||
401 | * \? --> \? --> \\? |
||
402 | * \- --> \- --> \\- |
||
403 | * ' --> \' --> \\\' |
||
404 | * " --> \" --> \\\" |
||
405 | * |
||
406 | * @param integer $parameterIndex the parameter index (one-based) |
||
407 | * @param string $string the CONTAINS string |
||
408 | * @throws CmisInvalidArgumentException If given value is not a string |
||
409 | */ |
||
410 | 10 | public function setStringContains($parameterIndex, $string) |
|
418 | |||
419 | /** |
||
420 | * Sets the designated parameter to the given string. |
||
421 | * It does not escape backslashes ('\') in front of '%' and '_'. |
||
422 | * |
||
423 | * @param integer $parameterIndex the parameter index (one-based) |
||
424 | * @param $string |
||
425 | * @throws CmisInvalidArgumentException If given value is not a string |
||
426 | */ |
||
427 | 10 | public function setStringLike($parameterIndex, $string) |
|
435 | |||
436 | /** |
||
437 | * Sets the designated parameter to the query name of the given type. |
||
438 | * |
||
439 | * @param integer $parameterIndex the parameter index (one-based) |
||
440 | * @param ObjectTypeInterface $type the object type |
||
441 | */ |
||
442 | 3 | public function setType($parameterIndex, ObjectTypeInterface $type) |
|
446 | |||
447 | /** |
||
448 | * Sets the designated parameter to the given value |
||
449 | * |
||
450 | * @param integer $parameterIndex |
||
451 | * @param mixed $value |
||
452 | * @throws CmisInvalidArgumentException If parameter index is not of type integer |
||
453 | */ |
||
454 | 49 | protected function setParameter($parameterIndex, $value) |
|
462 | |||
463 | /** |
||
464 | * Returns the query statement. |
||
465 | * |
||
466 | * @return string the query statement, not null |
||
467 | */ |
||
468 | 4 | public function toQueryString() |
|
494 | |||
495 | /** |
||
496 | * Escapes string for query |
||
497 | * |
||
498 | * @param $string |
||
499 | * @return string |
||
500 | */ |
||
501 | 29 | protected function escape($string) |
|
505 | |||
506 | /** |
||
507 | * Escapes string, but not escapes backslashes ('\') in front of '%' and '_'. |
||
508 | * |
||
509 | * @param $string |
||
510 | * @return string |
||
511 | */ |
||
512 | 19 | View Code Duplication | protected function escapeLike($string) |
522 | |||
523 | /** |
||
524 | * Escapes string, but not escapes backslashes ('\') in front of '*' and '?'. |
||
525 | * |
||
526 | * @param $string |
||
527 | * @return string |
||
528 | */ |
||
529 | 20 | View Code Duplication | protected function escapeContains($string) |
539 | } |
||
540 |
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.