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 HateoasBuilder 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 HateoasBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | class HateoasBuilder |
||
51 | { |
||
52 | /** |
||
53 | * @var SerializerBuilder |
||
54 | */ |
||
55 | private $serializerBuilder; |
||
56 | |||
57 | /** |
||
58 | * @var ExpressionLanguage |
||
59 | */ |
||
60 | private $expressionLanguage; |
||
61 | |||
62 | /** |
||
63 | * @var array |
||
64 | */ |
||
65 | private $contextVariables = array(); |
||
66 | |||
67 | /** |
||
68 | * ExpressionFunctionInterface[] |
||
69 | */ |
||
70 | private $expressionFunctions = array(); |
||
71 | |||
72 | /** |
||
73 | * @var XmlSerializerInterface |
||
74 | */ |
||
75 | private $xmlSerializer; |
||
76 | |||
77 | /** |
||
78 | * @var JsonSerializerInterface |
||
79 | */ |
||
80 | private $jsonSerializer; |
||
81 | |||
82 | /** |
||
83 | * @var UrlGeneratorRegistry |
||
84 | */ |
||
85 | private $urlGeneratorRegistry; |
||
86 | |||
87 | private $configurationExtensions = array(); |
||
88 | |||
89 | private $chainResolver; |
||
90 | |||
91 | private $metadataDirs = array(); |
||
92 | |||
93 | private $debug = false; |
||
94 | |||
95 | private $cacheDir; |
||
96 | |||
97 | private $annotationReader; |
||
98 | |||
99 | private $includeInterfaceMetadata = false; |
||
100 | |||
101 | /** |
||
102 | * @param SerializerBuilder $serializerBuilder |
||
103 | * |
||
104 | * @return HateoasBuilder |
||
105 | */ |
||
106 | public static function create(SerializerBuilder $serializerBuilder = null) |
||
110 | |||
111 | /** |
||
112 | * @return Hateoas |
||
113 | */ |
||
114 | public static function buildHateoas() |
||
120 | |||
121 | public function __construct(SerializerBuilder $serializerBuilder = null) |
||
130 | |||
131 | /** |
||
132 | * Build a configured Hateoas instance. |
||
133 | * |
||
134 | * @return Hateoas |
||
135 | */ |
||
136 | public function build() |
||
137 | { |
||
138 | $metadataFactory = $this->buildMetadataFactory(); |
||
139 | $relationProvider = new RelationProvider($metadataFactory, $this->chainResolver); |
||
140 | $relationsRepository = new RelationsRepository($metadataFactory, $relationProvider); |
||
141 | $expressionEvaluator = new ExpressionEvaluator($this->getExpressionLanguage(), $this->contextVariables); |
||
142 | $linkFactory = new LinkFactory($expressionEvaluator, $this->urlGeneratorRegistry); |
||
143 | $exclusionManager = new ExclusionManager($expressionEvaluator); |
||
144 | $linksFactory = new LinksFactory($relationsRepository, $linkFactory, $exclusionManager); |
||
145 | $embeddedsFactory = new EmbeddedsFactory($relationsRepository, $expressionEvaluator, $exclusionManager); |
||
146 | $linkHelper = new LinkHelper($linkFactory, $relationsRepository); |
||
147 | |||
148 | // Register Hateoas core functions |
||
149 | $expressionEvaluator->registerFunction(new LinkExpressionFunction($linkHelper)); |
||
150 | |||
151 | // Register user functions |
||
152 | foreach ($this->expressionFunctions as $expressionFunction) { |
||
153 | $expressionEvaluator->registerFunction($expressionFunction); |
||
154 | } |
||
155 | |||
156 | if (null === $this->xmlSerializer) { |
||
157 | $this->setDefaultXmlSerializer(); |
||
158 | } |
||
159 | |||
160 | if (null === $this->jsonSerializer) { |
||
161 | $this->setDefaultJsonSerializer(); |
||
162 | } |
||
163 | |||
164 | $inlineDeferrers = array(); |
||
165 | $eventSubscribers = array( |
||
166 | new XmlEventSubscriber($this->xmlSerializer, $linksFactory, $embeddedsFactory), |
||
167 | new JsonEventSubscriber( |
||
168 | $this->jsonSerializer, |
||
169 | $linksFactory, |
||
170 | $embeddedsFactory, |
||
171 | $inlineDeferrers[] = new InlineDeferrer(), |
||
172 | $inlineDeferrers[] = new InlineDeferrer() |
||
173 | ), |
||
174 | ); |
||
175 | |||
176 | $this->serializerBuilder |
||
177 | ->addDefaultListeners() |
||
178 | ->configureListeners(function (EventDispatcherInterface $dispatcher) use ($eventSubscribers) { |
||
179 | foreach ($eventSubscribers as $eventSubscriber) { |
||
180 | $dispatcher->addSubscriber($eventSubscriber); |
||
181 | } |
||
182 | }) |
||
183 | ; |
||
184 | |||
185 | $jmsSerializer = $this->serializerBuilder->build(); |
||
186 | foreach (array_merge($inlineDeferrers, array($this->jsonSerializer, $this->xmlSerializer)) as $serializer) { |
||
187 | if ($serializer instanceof JMSSerializerMetadataAwareInterface) { |
||
188 | $serializer->setMetadataFactory($jmsSerializer->getMetadataFactory()); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | return new Hateoas($jmsSerializer, $linkHelper); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * @param XmlSerializerInterface $xmlSerializer |
||
197 | * |
||
198 | * @return HateoasBuilder |
||
199 | */ |
||
200 | public function setXmlSerializer(XmlSerializerInterface $xmlSerializer) |
||
201 | { |
||
202 | $this->xmlSerializer = $xmlSerializer; |
||
203 | |||
204 | return $this; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Set the default XML serializer (`XmlSerializer`). |
||
209 | * |
||
210 | * @return HateoasBuilder |
||
211 | */ |
||
212 | public function setDefaultXmlSerializer() |
||
216 | |||
217 | /** |
||
218 | * @param JsonSerializerInterface $jsonSerializer |
||
219 | * |
||
220 | * @return HateoasBuilder |
||
221 | */ |
||
222 | public function setJsonSerializer(JsonSerializerInterface $jsonSerializer) |
||
223 | { |
||
224 | $this->jsonSerializer = $jsonSerializer; |
||
225 | |||
226 | return $this; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Set the default JSON serializer (`JsonHalSerializer`). |
||
231 | * |
||
232 | * @return HateoasBuilder |
||
233 | */ |
||
234 | public function setDefaultJsonSerializer() |
||
235 | { |
||
236 | return $this->setJsonSerializer(new JsonHalSerializer()); |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * Add a new URL generator. If you pass `null` as name, it will be the |
||
241 | * default URL generator. |
||
242 | * |
||
243 | * @param string|null $name |
||
244 | * @param UrlGeneratorInterface $urlGenerator |
||
245 | * |
||
246 | * @return HateoasBuilder |
||
247 | */ |
||
248 | public function setUrlGenerator($name, UrlGeneratorInterface $urlGenerator) |
||
254 | |||
255 | /** |
||
256 | * Add a new expression context variable. |
||
257 | * |
||
258 | * @param string $name |
||
259 | * @param mixed $value |
||
260 | * |
||
261 | * @return HateoasBuilder |
||
262 | */ |
||
263 | public function setExpressionContextVariable($name, $value) |
||
264 | { |
||
265 | $this->contextVariables[$name] = $value; |
||
266 | |||
267 | return $this; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * @param ExpressionLanguage $expressionLanguage |
||
272 | * |
||
273 | * @return HateoasBuilder |
||
274 | */ |
||
275 | public function setExpressionLanguage(ExpressionLanguage $expressionLanguage) |
||
276 | { |
||
277 | $this->expressionLanguage = $expressionLanguage; |
||
278 | |||
279 | return $this; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * @param ExpressionFunctionInterface $expressionFunction |
||
284 | * |
||
285 | * @return HateoasBuilder |
||
286 | */ |
||
287 | public function registerExpressionFunction(ExpressionFunctionInterface $expressionFunction) |
||
288 | { |
||
289 | $this->expressionFunctions[] = $expressionFunction; |
||
290 | |||
291 | return $this; |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * Add a new relation provider resolver. |
||
296 | * |
||
297 | * @param RelationProviderResolverInterface $resolver |
||
298 | * |
||
299 | * @return HateoasBuilder |
||
300 | */ |
||
301 | public function addRelationProviderResolver(RelationProviderResolverInterface $resolver) |
||
302 | { |
||
303 | $this->chainResolver->addResolver($resolver); |
||
304 | |||
305 | return $this; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * @param ConfigurationExtensionInterface $configurationExtension |
||
310 | * |
||
311 | * @return HateoasBuilder |
||
312 | */ |
||
313 | public function addConfigurationExtension(ConfigurationExtensionInterface $configurationExtension) |
||
314 | { |
||
315 | $this->configurationExtensions[] = $configurationExtension; |
||
316 | |||
317 | return $this; |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * @param boolean $debug |
||
322 | * |
||
323 | * @return HateoasBuilder |
||
324 | */ |
||
325 | public function setDebug($debug) |
||
326 | { |
||
327 | $this->debug = (boolean) $debug; |
||
328 | |||
329 | return $this; |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * @param string $dir |
||
334 | * |
||
335 | * @return HateoasBuilder |
||
336 | */ |
||
337 | public function setCacheDir($dir) |
||
338 | { |
||
339 | if (!is_dir($dir)) { |
||
340 | $this->createDir($dir); |
||
341 | } |
||
342 | |||
343 | if (!is_writable($dir)) { |
||
344 | throw new \InvalidArgumentException(sprintf('The cache directory "%s" is not writable.', $dir)); |
||
345 | } |
||
346 | |||
347 | $this->cacheDir = $dir; |
||
348 | |||
349 | return $this; |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * @param boolean $include Whether to include the metadata from the interfaces |
||
354 | * |
||
355 | * @return HateoasBuilder |
||
356 | */ |
||
357 | public function includeInterfaceMetadata($include) |
||
358 | { |
||
359 | $this->includeInterfaceMetadata = (boolean) $include; |
||
360 | |||
361 | return $this; |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * Set a map of namespace prefixes to directories. |
||
366 | * |
||
367 | * This method overrides any previously defined directories. |
||
368 | * |
||
369 | * @param array $namespacePrefixToDirMap |
||
370 | * |
||
371 | * @return HateoasBuilder |
||
372 | */ |
||
373 | public function setMetadataDirs(array $namespacePrefixToDirMap) |
||
385 | |||
386 | /** |
||
387 | * Add a directory where the serializer will look for class metadata. |
||
388 | * |
||
389 | * The namespace prefix will make the names of the actual metadata files a bit shorter. For example, let's assume |
||
390 | * that you have a directory where you only store metadata files for the ``MyApplication\Entity`` namespace. |
||
391 | * |
||
392 | * If you use an empty prefix, your metadata files would need to look like: |
||
393 | * |
||
394 | * ``my-dir/MyApplication.Entity.SomeObject.yml`` |
||
395 | * ``my-dir/MyApplication.Entity.OtherObject.yml`` |
||
396 | * |
||
397 | * If you use ``MyApplication\Entity`` as prefix, your metadata files would need to look like: |
||
398 | * |
||
399 | * ``my-dir/SomeObject.yml`` |
||
400 | * ``my-dir/OtherObject.yml`` |
||
401 | * |
||
402 | * Please keep in mind that you currently may only have one directory per namespace prefix. |
||
403 | * |
||
404 | * @param string $dir The directory where metadata files are located. |
||
405 | * @param string $namespacePrefix An optional prefix if you only store metadata for specific namespaces in this directory. |
||
406 | * |
||
407 | * @return HateoasBuilder |
||
408 | */ |
||
409 | View Code Duplication | public function addMetadataDir($dir, $namespacePrefix = '') |
|
423 | |||
424 | /** |
||
425 | * Add a map of namespace prefixes to directories. |
||
426 | * |
||
427 | * @param array $namespacePrefixToDirMap |
||
428 | * |
||
429 | * @return HateoasBuilder |
||
430 | */ |
||
431 | public function addMetadataDirs(array $namespacePrefixToDirMap) |
||
439 | |||
440 | /** |
||
441 | * Similar to addMetadataDir(), but overrides an existing entry. |
||
442 | * |
||
443 | * @param string $dir |
||
444 | * @param string $namespacePrefix |
||
445 | * |
||
446 | * @return HateoasBuilder |
||
447 | */ |
||
448 | View Code Duplication | public function replaceMetadataDir($dir, $namespacePrefix = '') |
|
462 | |||
463 | private function buildMetadataFactory() |
||
464 | { |
||
465 | $annotationReader = $this->annotationReader; |
||
466 | |||
467 | if (null === $annotationReader) { |
||
468 | $annotationReader = new AnnotationReader(); |
||
469 | |||
470 | if (null !== $this->cacheDir) { |
||
471 | $this->createDir($this->cacheDir.'/annotations'); |
||
472 | $annotationReader = new FileCacheReader($annotationReader, $this->cacheDir.'/annotations', $this->debug); |
||
473 | } |
||
474 | } |
||
498 | |||
499 | /** |
||
500 | * @param string $dir |
||
501 | */ |
||
502 | private function createDir($dir) |
||
503 | { |
||
504 | if (is_dir($dir)) { |
||
505 | return; |
||
506 | } |
||
507 | |||
508 | if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { |
||
509 | throw new \RuntimeException(sprintf('Could not create directory "%s".', $dir)); |
||
510 | } |
||
511 | } |
||
512 | |||
513 | private function getExpressionLanguage() |
||
521 | } |
||
522 |
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.