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 AbstractParserType 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 AbstractParserType, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | abstract class AbstractParserType implements ParserTypeInterface |
||
37 | { |
||
38 | /** |
||
39 | * @var ManagerRegistry |
||
40 | */ |
||
41 | protected $doctrine; |
||
42 | |||
43 | /** |
||
44 | * @var CachedSourceManager |
||
45 | */ |
||
46 | protected $sourceManager; |
||
47 | |||
48 | /** |
||
49 | * @var array |
||
50 | */ |
||
51 | protected $options = []; |
||
52 | |||
53 | /** |
||
54 | * @param ManagerRegistry $doctrine |
||
55 | * @param CachedSourceManager $sourceManager |
||
56 | */ |
||
57 | public function __construct(ManagerRegistry $doctrine, CachedSourceManager $sourceManager) |
||
58 | { |
||
59 | $this->doctrine = $doctrine; |
||
60 | $this->sourceManager = $sourceManager; |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * {@inheritDoc} |
||
65 | */ |
||
66 | View Code Duplication | public function setOptions(OptionsResolver $resolver) |
|
|
|||
67 | { |
||
68 | $resolver->setRequired([ |
||
69 | 'forced', |
||
70 | 'scraper', |
||
71 | 'date_locale', |
||
72 | 'number_locale', |
||
73 | 'default_values', |
||
74 | ]); |
||
75 | |||
76 | $resolver->setDefaults([ |
||
77 | 'forced' => false, |
||
78 | 'date_locale' => 'en', |
||
79 | 'number_locale' => 'en', |
||
80 | 'default_values' => [], |
||
81 | ]); |
||
82 | |||
83 | $resolver->setAllowedTypes('forced', 'bool'); |
||
84 | $resolver->setAllowedTypes('scraper', Scraper::class); |
||
85 | $resolver->setAllowedTypes('default_values', 'array'); |
||
86 | |||
87 | $resolver->setAllowedValues('date_locale', ['en', 'nl']); |
||
88 | $resolver->setAllowedValues('number_locale', ['en', 'nl']); |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * @inheritdoc |
||
93 | */ |
||
94 | public function build(ParserBuilderInterface $parser, array $options) |
||
95 | { |
||
96 | $this->options = $options; |
||
97 | |||
98 | // set original id/url on the item |
||
99 | $parser->addModifierBetween(new ScrapedItemBagMapper($this->getOriginalIdCallback(), $this->getOriginalUrlCallback(), $this->getModificationDateCallback()), 100, 200); |
||
100 | |||
101 | // range 300-400: perform validation and checks for skipping early on |
||
102 | $parser->addModifierBetween(new OriginIdValidator(), 300, 400); |
||
103 | $parser->addModifierBetween(new BlockedSourceFilter($this->sourceManager), 300, 400); |
||
104 | |||
105 | // check for modification dates, but only when not forced |
||
106 | if ($options['forced'] === false) { |
||
107 | $parser->addModifierBetween(new ModifiedItemFilter($this->sourceManager), 300, 400); |
||
108 | } |
||
109 | |||
110 | // 2000-3000: map paths |
||
111 | $parser->addModifier(new NodeMapper($this->getMapping()), 2000); |
||
112 | $parser->addModifierBetween(new TrimTransformer(), 2000, 3000); |
||
113 | |||
114 | // 3000-3500: transform specific fields |
||
115 | |||
116 | // 3500-4000: feed-type specific: reserved for field transformers before the regular transformers |
||
117 | |||
118 | // 4000-5000: reserved for transformers added automatically based on entity field mapping |
||
119 | $this->addEntityModifiers($parser, 4000, 5000); |
||
120 | |||
121 | // 5000-6000: feed-type specific: reserved for field transformers after the regular transformers |
||
122 | |||
123 | // 6000-7000: reserved for modifiers after all other modifiers are done |
||
124 | $this->addFinalModifiers($parser, 6000, 7000); |
||
125 | |||
126 | // give extending feed type a method for custom modifiers |
||
127 | $this->addCustomModifiers($parser, $options); |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @return array |
||
132 | */ |
||
133 | abstract protected function getMapping(); |
||
134 | |||
135 | /** |
||
136 | * Returns mapping for an association, or null if it does not exist. |
||
137 | * |
||
138 | * @param string $association |
||
139 | * |
||
140 | * @return array|null |
||
141 | */ |
||
142 | abstract protected function getAssociationMapping($association); |
||
143 | |||
144 | /** |
||
145 | * Returns mapping for a field, or null if it does not exist. |
||
146 | * |
||
147 | * @param string $field |
||
148 | * |
||
149 | * @return array|null |
||
150 | */ |
||
151 | abstract protected function getFieldMapping($field); |
||
152 | |||
153 | /** |
||
154 | * Returns an array with all the field/association names for |
||
155 | * the entity that is imported. |
||
156 | * |
||
157 | * @return array |
||
158 | */ |
||
159 | abstract protected function getEntityFields(); |
||
160 | |||
161 | /** |
||
162 | * Specify a mapping here from foreign configuration to our configuration. |
||
163 | * |
||
164 | * @return array |
||
165 | */ |
||
166 | abstract protected function getForeignMapping(); |
||
167 | |||
168 | /** |
||
169 | * @return \Closure |
||
170 | */ |
||
171 | View Code Duplication | protected function getOriginalIdCallback() |
|
187 | |||
188 | /** |
||
189 | * @return \Closure |
||
190 | */ |
||
191 | View Code Duplication | protected function getOriginalUrlCallback() |
|
207 | |||
208 | /** |
||
209 | * @return \Closure |
||
210 | */ |
||
211 | View Code Duplication | protected function getModificationDateCallback() |
|
229 | |||
230 | /** |
||
231 | * Override this method to add custom modifiers to the feed. |
||
232 | * |
||
233 | * @param ParserBuilderInterface $parser |
||
234 | * @param array $options |
||
235 | */ |
||
236 | protected function addCustomModifiers(ParserBuilderInterface $parser, array $options) |
||
239 | |||
240 | /** |
||
241 | * Automatically adds modifiers based on entity field/association mapping. |
||
242 | * |
||
243 | * @param ParserBuilderInterface $parser |
||
244 | * @param int $startIndex |
||
245 | * @param int $endIndex |
||
246 | */ |
||
247 | View Code Duplication | protected function addEntityModifiers(ParserBuilderInterface $parser, $startIndex, $endIndex) |
|
268 | |||
269 | /** |
||
270 | * @param ParserBuilderInterface $parser |
||
271 | * @param string $association The association name |
||
272 | * @param array $mapping The association mapping |
||
273 | * @param int $startIndex |
||
274 | * @param int $endIndex |
||
275 | * |
||
276 | * @return int The updated index |
||
277 | */ |
||
278 | View Code Duplication | protected function addAssociationModifiers( |
|
293 | |||
294 | /** |
||
295 | * @param ParserBuilderInterface $parser |
||
296 | * @param string $field The field name |
||
297 | * @param array $mapping The field mapping |
||
298 | * @param int $startIndex |
||
299 | * @param int $endIndex |
||
300 | */ |
||
301 | protected function addFieldModifiers( |
||
317 | |||
318 | /** |
||
319 | * @param ParserBuilderInterface $parser |
||
320 | * @param string $field The field name |
||
321 | * @param array $mapping The field mapping |
||
322 | * @param int $startIndex |
||
323 | * @param int $endIndex |
||
324 | */ |
||
325 | protected function addFieldTypeModifiers(ParserBuilderInterface $parser, $field, array $mapping, $startIndex, $endIndex) |
||
389 | |||
390 | /** |
||
391 | * @param ParserBuilderInterface $parser |
||
392 | * @param int $startStartIndex |
||
393 | * @param int $endIndex |
||
394 | */ |
||
395 | View Code Duplication | protected function addFinalModifiers(ParserBuilderInterface $parser, $startStartIndex, $endIndex) |
|
403 | |||
404 | /** |
||
405 | * Returns the names of all mapped and extra mapped fields. These are the |
||
406 | * fields that are allowed in the resulting item. The fields are |
||
407 | * normalized to be lowercased and underscored (instead of dashes). |
||
408 | * |
||
409 | * @return array |
||
410 | */ |
||
411 | View Code Duplication | protected function getMappedFields() |
|
434 | |||
435 | /** |
||
436 | * Specify fields here that are explicitly not mapped. |
||
437 | * |
||
438 | * @return array |
||
439 | */ |
||
440 | protected function getUnmappedFields() |
||
444 | |||
445 | /** |
||
446 | * Specify fields here that are not mapped directly, but need to stay in the resulting item. |
||
447 | * |
||
448 | * @return array |
||
449 | */ |
||
450 | protected function getExtraMappedFields() |
||
454 | |||
455 | /** |
||
456 | * @return string |
||
457 | */ |
||
458 | protected function getOriginalIdSelector() |
||
461 | |||
462 | /** |
||
463 | * @return string |
||
464 | */ |
||
465 | protected function getOriginalUrlSelector() |
||
468 | |||
469 | /** |
||
470 | * @return string |
||
471 | */ |
||
472 | protected function getModificationDateSelector() |
||
475 | |||
476 | /** |
||
477 | * @return EntityManagerInterface |
||
478 | */ |
||
479 | protected function getEntityManager() |
||
483 | } |
||
484 |
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.