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 EntityMetadata 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 EntityMetadata, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | class EntityMetadata implements ApiMetadata |
||
13 | { |
||
14 | /** |
||
15 | * The ReflectionProperty instances of the mapped class. |
||
16 | * |
||
17 | * @var \ReflectionProperty[] |
||
18 | */ |
||
19 | public $reflFields = []; |
||
20 | /** @var string */ |
||
21 | public $name; |
||
22 | /** @var string */ |
||
23 | public $namespace; |
||
24 | /** @var string */ |
||
25 | public $rootEntityName; |
||
26 | /** @var string[] */ |
||
27 | public $identifier = []; |
||
28 | /** @var array */ |
||
29 | public $fields = []; |
||
30 | /** @var array */ |
||
31 | public $associations = []; |
||
32 | /** @var string */ |
||
33 | public $repositoryClass = EntityRepository::class; |
||
34 | /** @var \ReflectionClass */ |
||
35 | public $reflClass; |
||
36 | /** @var MethodProviderInterface */ |
||
37 | public $methodProvider; |
||
38 | /** @var string */ |
||
39 | public $clientName; |
||
40 | /** @var string[] */ |
||
41 | public $apiFieldNames = []; |
||
42 | /** @var string[] */ |
||
43 | public $fieldNames = []; |
||
44 | /** @var bool */ |
||
45 | public $isMappedSuperclass = false; |
||
46 | /** @var bool */ |
||
47 | public $containsForeignIdentifier; |
||
48 | /** @var bool */ |
||
49 | public $isIdentifierComposite = false; |
||
50 | /** @var string */ |
||
51 | public $searcher; |
||
52 | /** @var string */ |
||
53 | public $finder; |
||
54 | /** @var InstantiatorInterface */ |
||
55 | private $instantiator; |
||
56 | |||
57 | /** |
||
58 | * Initializes a new ClassMetadata instance that will hold the object-relational mapping |
||
59 | * metadata of the class with the given name. |
||
60 | * |
||
61 | * @param string $entityName The name of the entity class the new instance is used for. |
||
62 | */ |
||
63 | 14 | public function __construct($entityName) |
|
68 | |||
69 | /** |
||
70 | * @return boolean |
||
71 | */ |
||
72 | public function containsForeignIdentifier() |
||
76 | |||
77 | /** {@inheritdoc} */ |
||
78 | 3 | public function getReflectionProperties() |
|
82 | |||
83 | /** |
||
84 | * {@inheritdoc} |
||
85 | */ |
||
86 | 12 | public function getReflectionProperty($name) |
|
94 | |||
95 | /** {@inheritdoc} */ |
||
96 | 14 | public function getName() |
|
97 | { |
||
98 | 14 | return $this->name; |
|
99 | } |
||
100 | |||
101 | /** {@inheritdoc} */ |
||
102 | 14 | public function getMethodContainer() |
|
106 | |||
107 | /** {@inheritdoc} */ |
||
108 | 13 | public function getRepositoryClass() |
|
112 | |||
113 | /** {@inheritdoc} */ |
||
114 | 2 | public function getIdentifier() |
|
118 | |||
119 | 6 | public function setIdentifier($identifier) |
|
124 | |||
125 | /** {@inheritdoc} */ |
||
126 | 12 | public function getReflectionClass() |
|
134 | |||
135 | /** {@inheritdoc} */ |
||
136 | public function isIdentifier($fieldName) |
||
140 | |||
141 | /** {@inheritdoc} */ |
||
142 | 2 | public function hasField($fieldName) |
|
146 | |||
147 | /** {@inheritdoc} */ |
||
148 | 12 | public function getFieldNames() |
|
152 | |||
153 | /** {@inheritdoc} */ |
||
154 | 13 | public function hasAssociation($fieldName) |
|
158 | |||
159 | /** {@inheritdoc} */ |
||
160 | 13 | public function getAssociationNames() |
|
164 | |||
165 | /** {@inheritdoc} */ |
||
166 | 9 | public function isSingleValuedAssociation($fieldName) |
|
170 | |||
171 | /** {@inheritdoc} */ |
||
172 | 9 | public function isCollectionValuedAssociation($fieldName) |
|
176 | |||
177 | /** {@inheritdoc} */ |
||
178 | 12 | public function getIdentifierFieldNames() |
|
182 | |||
183 | /** {@inheritdoc} */ |
||
184 | 12 | public function getTypeOfField($fieldName) |
|
188 | |||
189 | /** {@inheritdoc} */ |
||
190 | 9 | public function getAssociationTargetClass($assocName) |
|
194 | |||
195 | /** {@inheritdoc} */ |
||
196 | public function isAssociationInverseSide($assocName) |
||
202 | |||
203 | /** {@inheritdoc} */ |
||
204 | public function getAssociationMappedByTargetField($assocName) |
||
208 | |||
209 | /** {@inheritdoc} */ |
||
210 | 12 | public function getIdentifierValues($object) |
|
231 | |||
232 | /** {@inheritdoc} */ |
||
233 | 14 | public function wakeupReflection(ReflectionService $reflService) |
|
249 | |||
250 | /** {@inheritdoc} */ |
||
251 | 14 | public function initializeReflection(ReflectionService $reflService) |
|
259 | |||
260 | /** |
||
261 | * {@inheritdoc} |
||
262 | * @throws MappingException |
||
263 | */ |
||
264 | 14 | public function getClientName() |
|
265 | { |
||
266 | 14 | if (null === $this->clientName) { |
|
267 | throw MappingException::invalidClientName($this->getName()); |
||
268 | } |
||
269 | |||
270 | 14 | return $this->clientName; |
|
271 | } |
||
272 | |||
273 | 14 | public function mapField(array $mapping) |
|
274 | { |
||
275 | 14 | $this->validateAndCompleteFieldMapping($mapping); |
|
276 | 14 | $this->assertFieldNotMapped($mapping['field']); |
|
277 | 14 | $this->fields[$mapping['field']] = $mapping; |
|
278 | 14 | } |
|
279 | |||
280 | 14 | private function validateAndCompleteFieldMapping(array &$mapping) |
|
281 | { |
||
282 | 14 | if (!array_key_exists('api_field', $mapping)) { |
|
283 | 14 | $mapping['api_field'] = $mapping['field']; //todo: invent naming strategy |
|
284 | 14 | } |
|
285 | |||
286 | 14 | $this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
|
287 | 14 | $this->fieldNames[$mapping['api_field']] = $mapping['field']; |
|
288 | |||
289 | // Complete id mapping |
||
290 | 14 | View Code Duplication | if (isset($mapping['id']) && $mapping['id'] === true) { |
291 | 14 | if (!in_array($mapping['field'], $this->identifier, true)) { |
|
292 | 14 | $this->identifier[] = $mapping['field']; |
|
293 | 14 | } |
|
294 | // Check for composite key |
||
295 | 14 | if (!$this->isIdentifierComposite && count($this->identifier) > 1) { |
|
296 | 1 | $this->isIdentifierComposite = true; |
|
297 | 1 | } |
|
298 | 14 | } |
|
299 | |||
300 | 14 | } |
|
301 | |||
302 | /** |
||
303 | * @param string $fieldName |
||
304 | * |
||
305 | * @throws MappingException |
||
306 | */ |
||
307 | 14 | private function assertFieldNotMapped($fieldName) |
|
308 | { |
||
309 | 14 | if (array_key_exists($fieldName, $this->fields) || |
|
310 | 14 | array_key_exists($fieldName, $this->associations) || |
|
311 | 14 | array_key_exists($fieldName, $this->identifier) |
|
312 | 14 | ) { |
|
313 | throw new MappingException('Field already mapped'); |
||
314 | } |
||
315 | 14 | } |
|
316 | |||
317 | /** {@inheritdoc} */ |
||
318 | 4 | public function getFieldMapping($fieldName) |
|
326 | |||
327 | /** {@inheritdoc} */ |
||
328 | 9 | public function getAssociationMapping($fieldName) |
|
336 | |||
337 | 2 | public function setCustomRepositoryClass($customRepositoryClassName) |
|
341 | |||
342 | /** |
||
343 | * @internal |
||
344 | * |
||
345 | * @param array $mapping |
||
346 | * |
||
347 | * @return void |
||
348 | */ |
||
349 | 6 | public function addInheritedFieldMapping(array $mapping) |
|
355 | |||
356 | /** {@inheritdoc} */ |
||
357 | public function getFieldName($apiFieldName) |
||
361 | |||
362 | /** {@inheritdoc} */ |
||
363 | 13 | public function getApiFieldName($fieldName) |
|
367 | |||
368 | public function hasApiField($apiFieldName) |
||
372 | |||
373 | 10 | public function mapAssociation(array $mapping) |
|
374 | { |
||
375 | 10 | $mapping = $this->validateAndCompleteAssociationMapping($mapping); |
|
376 | 10 | $this->assertFieldNotMapped($mapping['field']); |
|
377 | 10 | $this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
|
378 | 10 | $this->fieldNames[$mapping['api_field']] = $mapping['field']; |
|
379 | 10 | $this->associations[$mapping['field']] = $mapping; |
|
380 | 10 | } |
|
381 | |||
382 | /** |
||
383 | * Validates & completes the basic mapping information that is common to all |
||
384 | * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). |
||
385 | * |
||
386 | * @param array $mapping The mapping. |
||
387 | * |
||
388 | * @return array The updated mapping. |
||
389 | * |
||
390 | * @throws MappingException If something is wrong with the mapping. |
||
391 | */ |
||
392 | 10 | protected function validateAndCompleteAssociationMapping(array $mapping) |
|
447 | |||
448 | /** {@inheritdoc} */ |
||
449 | 11 | public function newInstance() |
|
450 | { |
||
451 | 11 | return $this->instantiator->instantiate($this->name); |
|
452 | } |
||
453 | |||
454 | 5 | public function getSearcherClass() |
|
458 | |||
459 | 11 | public function getFinderClass() |
|
463 | |||
464 | 4 | public function isIdentifierComposite() |
|
468 | |||
469 | /** {@inheritdoc} */ |
||
470 | public function getRootEntityName() |
||
474 | |||
475 | /** |
||
476 | * Populates the entity identifier of an entity. |
||
477 | * |
||
478 | * @param object $entity |
||
479 | * @param array $id |
||
480 | * |
||
481 | * @return void |
||
482 | */ |
||
483 | public function assignIdentifier($entity, array $id) |
||
489 | |||
490 | 6 | public function addInheritedAssociationMapping(array $mapping) |
|
496 | } |
||
497 |
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.