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 YamlFileLoader 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 YamlFileLoader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class YamlFileLoader extends FileLoader |
||
33 | { |
||
34 | private $yamlParser; |
||
35 | |||
36 | /** |
||
37 | * {@inheritdoc} |
||
38 | */ |
||
39 | public function load($resource, $type = null) |
||
40 | { |
||
41 | $path = $this->locator->locate($resource); |
||
42 | |||
43 | $content = $this->loadFile($path); |
||
44 | |||
45 | $this->container->addResource(new FileResource($path)); |
||
46 | |||
47 | // empty file |
||
48 | if (null === $content) { |
||
49 | return; |
||
50 | } |
||
51 | |||
52 | // imports |
||
53 | $this->parseImports($content, $path); |
||
54 | |||
55 | // parameters |
||
56 | View Code Duplication | if (isset($content['parameters'])) { |
|
|
|||
57 | if (!is_array($content['parameters'])) { |
||
58 | throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource)); |
||
59 | } |
||
60 | |||
61 | foreach ($content['parameters'] as $key => $value) { |
||
62 | $this->container->setParameter($key, $this->resolveServices($value)); |
||
63 | } |
||
64 | } |
||
65 | |||
66 | // extensions |
||
67 | $this->loadFromExtensions($content); |
||
68 | |||
69 | // services |
||
70 | $this->parseDefinitions($content, $resource); |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * {@inheritdoc} |
||
75 | */ |
||
76 | public function supports($resource, $type = null) |
||
80 | |||
81 | /** |
||
82 | * Parses all imports. |
||
83 | * |
||
84 | * @param array $content |
||
85 | * @param string $file |
||
86 | */ |
||
87 | private function parseImports($content, $file) |
||
88 | { |
||
89 | if (!isset($content['imports'])) { |
||
90 | return; |
||
91 | } |
||
92 | |||
93 | if (!is_array($content['imports'])) { |
||
94 | throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file)); |
||
95 | } |
||
96 | |||
97 | foreach ($content['imports'] as $import) { |
||
98 | if (!is_array($import)) { |
||
99 | throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file)); |
||
100 | } |
||
101 | |||
102 | $this->setCurrentDir(dirname($file)); |
||
103 | $this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file); |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Parses definitions. |
||
109 | * |
||
110 | * @param array $content |
||
111 | * @param string $file |
||
112 | */ |
||
113 | View Code Duplication | private function parseDefinitions($content, $file) |
|
127 | |||
128 | /** |
||
129 | * Parses a definition. |
||
130 | * |
||
131 | * @param string $id |
||
132 | * @param array $service |
||
133 | * @param string $file |
||
134 | * |
||
135 | * @throws InvalidArgumentException When tags are invalid |
||
136 | */ |
||
137 | private function parseDefinition($id, $service, $file) |
||
291 | |||
292 | /** |
||
293 | * Loads a YAML file. |
||
294 | * |
||
295 | * @param string $file |
||
296 | * |
||
297 | * @return array The file content |
||
298 | * |
||
299 | * @throws InvalidArgumentException when the given file is not a local file or when it does not exist |
||
300 | */ |
||
301 | protected function loadFile($file) |
||
302 | { |
||
303 | if (!class_exists('Symfony\Component\Yaml\Parser')) { |
||
304 | throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); |
||
305 | } |
||
306 | |||
307 | if (!stream_is_local($file)) { |
||
308 | throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); |
||
309 | } |
||
310 | |||
311 | if (!file_exists($file)) { |
||
312 | throw new InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file)); |
||
313 | } |
||
314 | |||
315 | if (null === $this->yamlParser) { |
||
316 | $this->yamlParser = new YamlParser(); |
||
317 | } |
||
318 | |||
319 | return $this->validate($this->yamlParser->parse(file_get_contents($file)), $file); |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Validates a YAML file. |
||
324 | * |
||
325 | * @param mixed $content |
||
326 | * @param string $file |
||
327 | * |
||
328 | * @return array |
||
329 | * |
||
330 | * @throws InvalidArgumentException When service file is not valid |
||
331 | */ |
||
332 | private function validate($content, $file) |
||
333 | { |
||
334 | if (null === $content) { |
||
335 | return $content; |
||
336 | } |
||
337 | |||
338 | if (!is_array($content)) { |
||
339 | throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); |
||
340 | } |
||
341 | |||
342 | foreach ($content as $namespace => $data) { |
||
343 | if (in_array($namespace, array('imports', 'parameters', 'services'))) { |
||
344 | continue; |
||
345 | } |
||
346 | |||
347 | View Code Duplication | if (!$this->container->hasExtension($namespace)) { |
|
348 | $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions())); |
||
349 | throw new InvalidArgumentException(sprintf( |
||
350 | 'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', |
||
351 | $namespace, |
||
352 | $file, |
||
353 | $namespace, |
||
354 | $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none' |
||
355 | )); |
||
356 | } |
||
357 | } |
||
358 | |||
359 | return $content; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Resolves services. |
||
364 | * |
||
365 | * @param string|array $value |
||
366 | * |
||
367 | * @return array|string|Reference |
||
368 | */ |
||
369 | View Code Duplication | private function resolveServices($value) |
|
370 | { |
||
371 | if (is_array($value)) { |
||
372 | $value = array_map(array($this, 'resolveServices'), $value); |
||
373 | } elseif (is_string($value) && 0 === strpos($value, '@=')) { |
||
374 | return new Expression(substr($value, 2)); |
||
375 | } elseif (is_string($value) && 0 === strpos($value, '@')) { |
||
376 | if (0 === strpos($value, '@@')) { |
||
377 | $value = substr($value, 1); |
||
378 | $invalidBehavior = null; |
||
379 | } elseif (0 === strpos($value, '@?')) { |
||
380 | $value = substr($value, 2); |
||
381 | $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; |
||
382 | } else { |
||
383 | $value = substr($value, 1); |
||
384 | $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; |
||
385 | } |
||
386 | |||
387 | if ('=' === substr($value, -1)) { |
||
388 | $value = substr($value, 0, -1); |
||
389 | $strict = false; |
||
390 | } else { |
||
391 | $strict = true; |
||
392 | } |
||
393 | |||
394 | if (null !== $invalidBehavior) { |
||
395 | $value = new Reference($value, $invalidBehavior, $strict); |
||
396 | } |
||
397 | } |
||
398 | |||
399 | return $value; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Loads from Extensions. |
||
404 | * |
||
405 | * @param array $content |
||
406 | */ |
||
407 | private function loadFromExtensions($content) |
||
421 | } |
||
422 |
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.