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 DocumentMap 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 DocumentMap, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class DocumentMap |
||
18 | { |
||
19 | /** |
||
20 | * @var array |
||
21 | */ |
||
22 | private $mappings = []; |
||
23 | /** |
||
24 | * @var Document[] |
||
25 | */ |
||
26 | private $documents = []; |
||
27 | |||
28 | /** |
||
29 | * Constructor |
||
30 | * |
||
31 | * @param Finder $doctrineFinder Doctrine mapping finder |
||
32 | * @param Finder $serializerFinder Serializer mapping finder |
||
33 | * @param Finder $validationFinder Validation mapping finder |
||
34 | */ |
||
35 | 7 | public function __construct(Finder $doctrineFinder, Finder $serializerFinder, Finder $validationFinder) |
|
49 | |||
50 | /** |
||
51 | * Get document |
||
52 | * |
||
53 | * @param string $className Document class |
||
54 | * @return Document |
||
55 | */ |
||
56 | 7 | public function getDocument($className) |
|
57 | { |
||
58 | 7 | if (isset($this->documents[$className])) { |
|
59 | 7 | return $this->documents[$className]; |
|
60 | } |
||
61 | 7 | if (!isset($this->mappings[$className])) { |
|
62 | throw new \InvalidArgumentException(sprintf('No XML mapping found for document "%s"', $className)); |
||
63 | } |
||
64 | |||
65 | 7 | return $this->documents[$className] = $this->processDocument( |
|
66 | 7 | $className, |
|
67 | 7 | $this->mappings[$className]['doctrine'], |
|
68 | 7 | $this->mappings[$className]['serializer'], |
|
69 | 7 | $this->mappings[$className]['validation'] |
|
70 | 7 | ); |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * Get all documents |
||
75 | * |
||
76 | * @return Document[] |
||
77 | */ |
||
78 | 5 | public function getDocuments() |
|
82 | |||
83 | /** |
||
84 | * Process document |
||
85 | * |
||
86 | * @param string $className Class name |
||
87 | * @param \DOMElement $doctrineMapping Doctrine XML mapping |
||
88 | * @param \DOMElement $serializerMapping Serializer XML mapping |
||
|
|||
89 | * @param \DOMElement $validationMapping Validation XML mapping |
||
90 | * @return Document |
||
91 | */ |
||
92 | 7 | private function processDocument( |
|
189 | |||
190 | /** |
||
191 | * Load doctrine class map |
||
192 | * |
||
193 | 7 | * @param Finder $finder Mapping finder |
|
194 | * @return array |
||
195 | 7 | */ |
|
196 | 7 | View Code Duplication | private function loadDoctrineClassMap(Finder $finder) |
218 | |||
219 | /** |
||
220 | * Load serializer class map |
||
221 | * |
||
222 | 7 | * @param Finder $finder Mapping finder |
|
223 | * @return array |
||
224 | 7 | */ |
|
225 | 7 | View Code Duplication | private function loadSerializerClassMap(Finder $finder) |
246 | |||
247 | /** |
||
248 | * Load validation class map |
||
249 | * |
||
250 | 7 | * @param Finder $finder Mapping finder |
|
251 | * @return array |
||
252 | 7 | */ |
|
253 | 7 | View Code Duplication | private function loadValidationClassMap(Finder $finder) |
275 | |||
276 | /** |
||
277 | * Get serializer fields |
||
278 | * |
||
279 | 7 | * @param \DOMElement $mapping Serializer XML mapping |
|
280 | * @return array |
||
281 | 7 | */ |
|
282 | private function getSerializerFields(\DOMElement $mapping) |
||
299 | |||
300 | /** |
||
301 | * Get serializer field type |
||
302 | 7 | * |
|
303 | * @param \DOMElement $field Field node |
||
304 | 7 | * @return string|null |
|
305 | 7 | */ |
|
306 | private function getSerializerFieldType(\DOMElement $field) |
||
317 | |||
318 | /** |
||
319 | * Get validation fields |
||
320 | 7 | * |
|
321 | * @param \DOMElement $mapping Validation XML mapping |
||
322 | 7 | * @return array |
|
323 | 7 | */ |
|
324 | private function getValidationFields(\DOMElement $mapping) |
||
340 | |||
341 | /** |
||
342 | * Get doctrine document fields |
||
343 | 7 | * |
|
344 | * @param \DOMElement $mapping Doctrine XML mapping |
||
345 | 7 | * @return array |
|
346 | 7 | */ |
|
347 | View Code Duplication | private function getDoctrineFields(\DOMElement $mapping) |
|
362 | |||
363 | /** |
||
364 | * Get doctrine document embed-one fields |
||
365 | 7 | * |
|
366 | * @param \DOMElement $mapping Doctrine XML mapping |
||
367 | 7 | * @return array |
|
368 | 7 | */ |
|
369 | View Code Duplication | private function getDoctrineEmbedOneFields(\DOMElement $mapping) |
|
384 | |||
385 | /** |
||
386 | * Get doctrine document embed-many fields |
||
387 | 7 | * |
|
388 | * @param \DOMElement $mapping Doctrine XML mapping |
||
389 | 7 | * @return array |
|
390 | 7 | */ |
|
391 | View Code Duplication | private function getDoctrineEmbedManyFields(\DOMElement $mapping) |
|
406 | } |
||
407 |
This check looks for
@param
annotations where the type inferred by our type inference engine differs from the declared type.It makes a suggestion as to what type it considers more descriptive.
Most often this is a case of a parameter that can be null in addition to its declared types.