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 EntityUtility 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 EntityUtility, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class EntityUtility |
||
20 | { |
||
21 | /** |
||
22 | * @var RestConfiguration |
||
23 | */ |
||
24 | private $config; |
||
25 | |||
26 | /** |
||
27 | * @var Inflector |
||
28 | */ |
||
29 | private $inflector; |
||
30 | |||
31 | /** |
||
32 | * @var TypeFactory |
||
33 | */ |
||
34 | private $typeFactory; |
||
35 | |||
36 | /** |
||
37 | * Constructor. |
||
38 | * |
||
39 | * @param RestConfiguration $config |
||
40 | */ |
||
41 | public function __construct(RestConfiguration $config, TypeFactory $typeFactory) |
||
47 | |||
48 | /** |
||
49 | * Gets the validator service. |
||
50 | * |
||
51 | * @return Validator |
||
52 | */ |
||
53 | public function getValidator() |
||
57 | |||
58 | /** |
||
59 | * Gets the REST configuration object. |
||
60 | * |
||
61 | * @return RestConfiguration |
||
62 | */ |
||
63 | public function getRestConfig() |
||
67 | |||
68 | /** |
||
69 | * Determines if a field key is valid, based on configuration. |
||
70 | * |
||
71 | * @param string $value |
||
72 | * @return bool |
||
73 | */ |
||
74 | public function isFieldKeyValid($value) |
||
78 | |||
79 | /** |
||
80 | * Determines if an entity type, based on configuration. |
||
81 | * |
||
82 | * @param string $value |
||
83 | * @return bool |
||
84 | */ |
||
85 | public function isEntityTypeValid($value) |
||
89 | |||
90 | /** |
||
91 | * Validates EntityMetadata. |
||
92 | * |
||
93 | * @param string $requestedType |
||
94 | * @param EntityMetadata $metadata |
||
95 | * @param MetadataFactory $mf |
||
96 | * @return bool |
||
97 | * @throws MetadataException |
||
98 | */ |
||
99 | public function validateMetadata($requestedType, EntityMetadata $metadata, MetadataFactory $mf) |
||
100 | { |
||
101 | if ($requestedType !== $metadata->type) { |
||
102 | throw MetadataException::invalidMetadata($requestedType, 'Metadata type mismatch.'); |
||
103 | } |
||
104 | $this->validateMetadataType($metadata); |
||
105 | |||
106 | if (null === $metadata->persistence) { |
||
107 | throw MetadataException::invalidMetadata($requestedType, 'No persistence metadata was found. All models must use a persistence layer.'); |
||
108 | } |
||
109 | |||
110 | $this->validateMetadataInheritance($metadata, $mf); |
||
111 | $this->validateMetadataAttributes($metadata, $mf); |
||
112 | $this->validateMetadataRelationships($metadata, $mf); |
||
113 | $this->validateMetadataEmbeds($metadata, $mf); |
||
114 | return true; |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Validates the proper entity type on EntityMetadata. |
||
119 | * |
||
120 | * @param EntityMetadata $metadata |
||
121 | * @return bool |
||
122 | * @throws MetadataException |
||
123 | */ |
||
124 | public function validateMetadataType(EntityMetadata $metadata) |
||
137 | |||
138 | /** |
||
139 | * Validates the proper entity inheritance on EntityMetadata. |
||
140 | * |
||
141 | * @param EntityMetadata $metadata |
||
142 | * @param MetadataFactory $mf |
||
143 | * @return bool |
||
144 | * @throws MetadataException |
||
145 | */ |
||
146 | public function validateMetadataInheritance(EntityMetadata $metadata, MetadataFactory $mf) |
||
185 | |||
186 | /** |
||
187 | * Validates entity attributes on the AttributeInterface. |
||
188 | * |
||
189 | * @param AttributeInterface $metadata |
||
190 | * @param MetadataFactory $mf |
||
191 | * @return bool |
||
192 | * @throws MetadataException |
||
193 | */ |
||
194 | public function validateMetadataAttributes(AttributeInterface $metadata, MetadataFactory $mf) |
||
195 | { |
||
196 | $type = $metadata instanceof EntityMetadata ? $metadata->type : (property_exists($metadata, 'name') ? $metadata->name : ''); |
||
197 | foreach ($metadata->getAttributes() as $key => $attribute) { |
||
198 | |||
199 | if ($key != $attribute->key) { |
||
200 | throw MetadataException::invalidMetadata($type, sprintf('Attribute key mismtach. "%s" !== "%s"', $key, $attribute->key)); |
||
201 | } |
||
202 | |||
203 | View Code Duplication | if (false === $this->isFieldKeyValid($attribute->key)) { |
|
204 | throw MetadataException::invalidMetadata($type, sprintf('The attribute key "%s" is invalid based on the configured name format "%s"', $attribute->key, $this->config->getFieldKeyFormat())); |
||
205 | } |
||
206 | |||
207 | if (false === $this->typeFactory->hasType($attribute->dataType)) { |
||
208 | throw MetadataException::invalidMetadata($type, sprintf('The data type "%s" for attribute "%s" is invalid', $attribute->dataType, $attribute->key)); |
||
209 | } |
||
210 | |||
211 | View Code Duplication | if ($metadata instanceof EntityMetadata && true === $metadata->isChildEntity()) { |
|
212 | $parent = $mf->getMetadataForType($metadata->extends); |
||
213 | if ($parent->hasAttribute($attribute->key)) { |
||
214 | throw MetadataException::invalidMetadata($type, sprintf('Parent entity type "%s" already contains attribute field "%s"', $parent->type, $attribute->key)); |
||
215 | } |
||
216 | } |
||
217 | |||
218 | if (true === $attribute->isCalculated()) { |
||
219 | if (false === class_exists($attribute->calculated['class']) || false === method_exists($attribute->calculated['class'], $attribute->calculated['method'])) { |
||
220 | throw MetadataException::invalidMetadata($type, sprintf('The attribute field "%s" is calculated, but was unable to find method "%s:%s"', $attribute->key, $attribute->calculated['class'], $attribute->calculated['method'])); |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | return true; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Validates entity relationships on EntityMetadata. |
||
229 | * |
||
230 | * @param EmbedInterface $metadata |
||
231 | * @param MetadataFactory $mf |
||
232 | * @return bool |
||
233 | */ |
||
234 | public function validateMetadataEmbeds(EmbedInterface $metadata, MetadataFactory $mf) |
||
235 | { |
||
236 | foreach ($metadata->getEmbeds() as $key => $embeddedProp) { |
||
237 | $this->validateMetadataAttributes($embeddedProp->embedMeta, $mf); |
||
238 | if ($metadata !== $embeddedProp->embedMeta) { |
||
239 | // Only re-validate if the embedded prop reference isn't the same as the parent. |
||
240 | $this->validateMetadataEmbeds($embeddedProp->embedMeta, $mf); |
||
241 | } |
||
242 | } |
||
243 | return true; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Validates entity relationships on EntityMetadata. |
||
248 | * |
||
249 | * @param EntityMetadata $metadata |
||
250 | * @param MetadataFactory $mf |
||
251 | * @return bool |
||
252 | * @throws MetadataException |
||
253 | */ |
||
254 | public function validateMetadataRelationships(EntityMetadata $metadata, MetadataFactory $mf) |
||
298 | |||
299 | /** |
||
300 | * Formats an entity type, based on configuration. |
||
301 | * |
||
302 | * @param string $type |
||
303 | * @return string |
||
304 | */ |
||
305 | public function formatEntityType($type) |
||
309 | |||
310 | /** |
||
311 | * Formats a field key, based on configuration. |
||
312 | * |
||
313 | * @param string $key |
||
314 | * @return string |
||
315 | */ |
||
316 | public function formatFieldKey($key) |
||
320 | |||
321 | /** |
||
322 | * Formats a value, based on a formatting type. |
||
323 | * |
||
324 | * @param string $format |
||
325 | * @param string $value |
||
326 | * @return string |
||
327 | * @throws RuntimeException |
||
328 | */ |
||
329 | protected function formatValue($format, $value) |
||
344 | } |
||
345 |
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.