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 Formatter 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 Formatter, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | final class Formatter |
||
23 | { |
||
24 | /** |
||
25 | * Query operators. |
||
26 | * Organized into handling groups. |
||
27 | * |
||
28 | * @var array |
||
29 | */ |
||
30 | private $ops = [ |
||
31 | 'root' => ['$and', '$or', '$nor'], |
||
32 | 'single' => ['$eq', '$gt', '$gte', '$lt', '$lte', '$ne'], |
||
33 | 'multiple' => ['$in', '$nin', '$all'], |
||
34 | 'recursive' => ['$not', '$elemMatch'], |
||
35 | 'ignore' => ['$exists', '$type', '$mod', '$size', '$regex', '$text', '$where'], |
||
36 | ]; |
||
37 | |||
38 | /** |
||
39 | * Formats a set of query criteria for a Model. |
||
40 | * Ensures the id and type fields are properly applied. |
||
41 | * Ensures that values are properly converted to their database equivalents: e.g dates, mongo ids, etc. |
||
42 | * |
||
43 | * @param EntityMetadata $metadata |
||
44 | * @param Store $store |
||
45 | * @param array $criteria |
||
46 | * @return array |
||
47 | */ |
||
48 | public function formatQuery(EntityMetadata $metadata, Store $store, array $criteria) |
||
70 | |||
71 | /** |
||
72 | * Prepares and formats an attribute value for proper insertion into the database. |
||
73 | * |
||
74 | * @param AttributeMetadata $attrMeta |
||
75 | * @param mixed $value |
||
76 | * @return mixed |
||
77 | */ |
||
78 | View Code Duplication | public function getAttributeDbValue(AttributeMetadata $attrMeta, $value) |
|
86 | |||
87 | /** |
||
88 | * Prepares and formats a has-one relationship model for proper insertion into the database. |
||
89 | * |
||
90 | * @param RelationshipMetadata $relMeta |
||
91 | * @param Model|null $model |
||
92 | * @return mixed |
||
93 | */ |
||
94 | View Code Duplication | public function getHasOneDbValue(RelationshipMetadata $relMeta, Model $model = null) |
|
101 | |||
102 | /** |
||
103 | * Prepares and formats a has-many relationship model set for proper insertion into the database. |
||
104 | * |
||
105 | * @param RelationshipMetadata $relMeta |
||
106 | * @param Model[]|null $models |
||
107 | * @return mixed |
||
108 | */ |
||
109 | View Code Duplication | public function getHasManyDbValue(RelationshipMetadata $relMeta, array $models = null) |
|
120 | |||
121 | /** |
||
122 | * {@inheritDoc} |
||
123 | */ |
||
124 | public function getIdentifierDbValue($identifier, $strategy = null) |
||
134 | |||
135 | /** |
||
136 | * Gets all possible identifier field keys (internal and persistence layer). |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | public function getIdentifierFields() |
||
144 | |||
145 | /** |
||
146 | * Gets all possible model type keys (internal and persistence layer). |
||
147 | * |
||
148 | * @return array |
||
149 | */ |
||
150 | public function getTypeFields() |
||
154 | |||
155 | /** |
||
156 | * Determines if a field key is an identifier. |
||
157 | * Uses both the internal and persistence identifier keys. |
||
158 | * |
||
159 | * @param string $key |
||
160 | * @return bool |
||
161 | */ |
||
162 | public function isIdentifierField($key) |
||
166 | |||
167 | /** |
||
168 | * Determines if the provided id strategy is supported. |
||
169 | * |
||
170 | * @param string|null $strategy |
||
171 | * @return bool |
||
172 | */ |
||
173 | public function isIdStrategySupported($strategy) |
||
177 | |||
178 | /** |
||
179 | * Determines if a field key is a model type field. |
||
180 | * Uses both the internal and persistence model type keys. |
||
181 | * |
||
182 | * @param string $key |
||
183 | * @return bool |
||
184 | */ |
||
185 | public function isTypeField($key) |
||
189 | |||
190 | /** |
||
191 | * Creates a reference for storage of a related model in the database |
||
192 | * |
||
193 | * @param RelationshipMetadata $relMeta |
||
194 | * @param Model $model |
||
195 | * @return mixed |
||
196 | */ |
||
197 | private function createReference(RelationshipMetadata $relMeta, Model $model) |
||
207 | |||
208 | /** |
||
209 | * Formats a query element and ensures the correct key and value are set. |
||
210 | * Returns a tuple of the formatted key and value. |
||
211 | * |
||
212 | * @param string $key |
||
213 | * @param mixed $value |
||
214 | * @param EntityMetadata $metadata |
||
215 | * @param Store $store |
||
216 | * @return array |
||
217 | */ |
||
218 | private function formatQueryElement($key, $value, EntityMetadata $metadata, Store $store) |
||
248 | |||
249 | /** |
||
250 | * Formats an attribute query element. |
||
251 | * Returns a tuple of the formatted key and value, or null if the key is not an attribute field. |
||
252 | * |
||
253 | * @param string $key |
||
254 | * @param mixed $value |
||
255 | * @param EntityMetadata $metadata |
||
256 | * @param Store $store |
||
257 | * @return array|null |
||
258 | */ |
||
259 | private function formatQueryElementAttr($key, $value, EntityMetadata $metadata, Store $store) |
||
281 | |||
282 | /** |
||
283 | * Formats a dot-notated field. |
||
284 | * Returns a tuple of the formatted key and value, or null if the key is not a dot-notated field, or cannot be handled. |
||
285 | * |
||
286 | * @param string $key |
||
287 | * @param mixed $value |
||
288 | * @param EntityMetadata $metadata |
||
289 | * @param Store $store |
||
290 | * @return array|null |
||
291 | */ |
||
292 | private function formatQueryElementDotted($key, $value, EntityMetadata $metadata, Store $store) |
||
327 | |||
328 | /** |
||
329 | * Formats an identifier query element. |
||
330 | * Returns a tuple of the formatted key and value, or null if the key is not an identifier field. |
||
331 | * |
||
332 | * @param string $key |
||
333 | * @param mixed $value |
||
334 | * @param EntityMetadata $metadata |
||
335 | * @return array|null |
||
336 | */ |
||
337 | View Code Duplication | private function formatQueryElementId($key, $value, EntityMetadata $metadata) |
|
352 | |||
353 | /** |
||
354 | * Formats a relationship query element. |
||
355 | * Returns a tuple of the formatted key and value, or null if the key is not a relationship field. |
||
356 | * |
||
357 | * @param string $key |
||
358 | * @param mixed $value |
||
359 | * @param EntityMetadata $metadata |
||
360 | * @param Store $store |
||
361 | * @return array|null |
||
362 | */ |
||
363 | private function formatQueryElementRel($key, $value, EntityMetadata $metadata, Store $store) |
||
382 | |||
383 | /** |
||
384 | * Formats a model type query element. |
||
385 | * Returns a tuple of the formatted key and value, or null if the key is not a type field. |
||
386 | * |
||
387 | * @param string $key |
||
388 | * @param mixed $value |
||
389 | * @return array|null |
||
390 | */ |
||
391 | View Code Duplication | private function formatQueryElementType($key, $value) |
|
406 | |||
407 | /** |
||
408 | * Formats a query expression. |
||
409 | * |
||
410 | * @param array $expression |
||
411 | * @param Closure $converter |
||
412 | * @return array |
||
413 | */ |
||
414 | private function formatQueryExpression(array $expression, Closure $converter) |
||
445 | |||
446 | /** |
||
447 | * Gets the converter for handling attribute values in queries. |
||
448 | * |
||
449 | * @param Store $store |
||
450 | * @param AttributeMetadata $attrMeta |
||
451 | * @return Closure |
||
452 | */ |
||
453 | private function getQueryAttrConverter(Store $store, AttributeMetadata $attrMeta) |
||
461 | |||
462 | /** |
||
463 | * Gets the converter for handling identifier values in queries. |
||
464 | * |
||
465 | * @param EntityMetadata $metadata |
||
466 | * @return Closure |
||
467 | */ |
||
468 | private function getQueryIdConverter(EntityMetadata $metadata) |
||
474 | |||
475 | /** |
||
476 | * Gets the converter for handling relationship values in queries. |
||
477 | * |
||
478 | * @param Store $store |
||
479 | * @param RelationshipMetadata $relMeta |
||
480 | * @return Closure |
||
481 | */ |
||
482 | private function getQueryRelConverter(Store $store, RelationshipMetadata $relMeta) |
||
487 | |||
488 | /** |
||
489 | * Gets the converter for handling model type values in queries. |
||
490 | * |
||
491 | * @return Closure |
||
492 | */ |
||
493 | private function getQueryTypeConverter() |
||
499 | |||
500 | /** |
||
501 | * Determines whether a query value has additional query operators. |
||
502 | * |
||
503 | * @param mixed $value |
||
504 | * @return bool |
||
505 | */ |
||
506 | private function hasOperators($value) |
||
524 | |||
525 | /** |
||
526 | * Determines if a key is a query operator. |
||
527 | * |
||
528 | * @param string $key |
||
529 | * @return bool |
||
530 | */ |
||
531 | private function isOperator($key) |
||
535 | |||
536 | /** |
||
537 | * Determines if a key is of a certain operator handling type. |
||
538 | * |
||
539 | * @param string $type |
||
540 | * @param string $key |
||
541 | * @return bool |
||
542 | */ |
||
543 | private function isOpType($type, $key) |
||
550 | } |
||
551 |
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.