YamlFileLoader   D
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 390
Duplicated Lines 38.46 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 85
c 1
b 0
f 0
lcom 1
cbo 4
dl 150
loc 390
rs 4.8717

9 Methods

Rating   Name   Duplication   Size   Complexity  
A supports() 0 4 2
A parseDefinitions() 14 14 4
F parseDefinition() 85 154 42
A loadFromExtensions() 0 14 4
B load() 9 33 5
B parseImports() 0 19 6
B loadFile() 0 20 5
C validate() 10 29 7
D resolveServices() 32 32 10

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\DependencyInjection\Loader;
13
14
use Symfony\Component\DependencyInjection\DefinitionDecorator;
15
use Symfony\Component\DependencyInjection\Alias;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
17
use Symfony\Component\DependencyInjection\Definition;
18
use Symfony\Component\DependencyInjection\Reference;
19
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
20
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
21
use Symfony\Component\Config\Resource\FileResource;
22
use Symfony\Component\Yaml\Parser as YamlParser;
23
use Symfony\Component\ExpressionLanguage\Expression;
24
25
/**
26
 * YamlFileLoader loads YAML files service definitions.
27
 *
28
 * The YAML format does not support anonymous services (cf. the XML loader).
29
 *
30
 * @author Fabien Potencier <[email protected]>
31
 */
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'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
77
    {
78
        return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true);
79
    }
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)
114
    {
115
        if (!isset($content['services'])) {
116
            return;
117
        }
118
119
        if (!is_array($content['services'])) {
120
            throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
121
        }
122
123
        foreach ($content['services'] as $id => $service) {
124
            $this->parseDefinition($id, $service, $file);
125
        }
126
    }
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)
138
    {
139 View Code Duplication
        if (is_string($service) && 0 === strpos($service, '@')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
140
            $this->container->setAlias($id, substr($service, 1));
141
142
            return;
143
        }
144
145 View Code Duplication
        if (!is_array($service)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
146
            throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file));
147
        }
148
149 View Code Duplication
        if (isset($service['alias'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
150
            $public = !array_key_exists('public', $service) || (bool) $service['public'];
151
            $this->container->setAlias($id, new Alias($service['alias'], $public));
152
153
            return;
154
        }
155
156 View Code Duplication
        if (isset($service['parent'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
157
            $definition = new DefinitionDecorator($service['parent']);
158
        } else {
159
            $definition = new Definition();
160
        }
161
162
        if (isset($service['class'])) {
163
            $definition->setClass($service['class']);
164
        }
165
166
        if (isset($service['scope'])) {
167
            $definition->setScope($service['scope']);
168
        }
169
170
        if (isset($service['synthetic'])) {
171
            $definition->setSynthetic($service['synthetic']);
172
        }
173
174
        if (isset($service['synchronized'])) {
175
            trigger_error(sprintf('The "synchronized" key in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED);
176
            $definition->setSynchronized($service['synchronized'], 'request' !== $id);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Depend...tion::setSynchronized() has been deprecated with message: since version 2.7, will be removed in 3.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
177
        }
178
179
        if (isset($service['lazy'])) {
180
            $definition->setLazy($service['lazy']);
181
        }
182
183
        if (isset($service['public'])) {
184
            $definition->setPublic($service['public']);
185
        }
186
187
        if (isset($service['abstract'])) {
188
            $definition->setAbstract($service['abstract']);
189
        }
190
191 View Code Duplication
        if (isset($service['factory'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
192
            if (is_string($service['factory'])) {
193
                if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) {
194
                    $parts = explode(':', $service['factory']);
195
                    $definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1]));
196
                } else {
197
                    $definition->setFactory($service['factory']);
198
                }
199
            } else {
200
                $definition->setFactory(array($this->resolveServices($service['factory'][0]), $service['factory'][1]));
201
            }
202
        }
203
204
        if (isset($service['factory_class'])) {
205
            trigger_error(sprintf('The "factory_class" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED);
206
            $definition->setFactoryClass($service['factory_class']);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Depend...tion::setFactoryClass() has been deprecated with message: since version 2.6, to be removed in 3.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
207
        }
208
209
        if (isset($service['factory_method'])) {
210
            trigger_error(sprintf('The "factory_method" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED);
211
            $definition->setFactoryMethod($service['factory_method']);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Depend...ion::setFactoryMethod() has been deprecated with message: since version 2.6, to be removed in 3.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
212
        }
213
214
        if (isset($service['factory_service'])) {
215
            trigger_error(sprintf('The "factory_service" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED);
216
            $definition->setFactoryService($service['factory_service']);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Depend...on::setFactoryService() has been deprecated with message: since version 2.6, to be removed in 3.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
217
        }
218
219
        if (isset($service['file'])) {
220
            $definition->setFile($service['file']);
221
        }
222
223
        if (isset($service['arguments'])) {
224
            $definition->setArguments($this->resolveServices($service['arguments']));
225
        }
226
227
        if (isset($service['properties'])) {
228
            $definition->setProperties($this->resolveServices($service['properties']));
229
        }
230
231 View Code Duplication
        if (isset($service['configurator'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
232
            if (is_string($service['configurator'])) {
233
                $definition->setConfigurator($service['configurator']);
234
            } else {
235
                $definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1]));
236
            }
237
        }
238
239 View Code Duplication
        if (isset($service['calls'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
240
            if (!is_array($service['calls'])) {
241
                throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
242
            }
243
244
            foreach ($service['calls'] as $call) {
245
                if (isset($call['method'])) {
246
                    $method = $call['method'];
247
                    $args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
248
                } else {
249
                    $method = $call[0];
250
                    $args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
251
                }
252
253
                $definition->addMethodCall($method, $args);
254
            }
255
        }
256
257 View Code Duplication
        if (isset($service['tags'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
258
            if (!is_array($service['tags'])) {
259
                throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
260
            }
261
262
            foreach ($service['tags'] as $tag) {
263
                if (!is_array($tag)) {
264
                    throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
265
                }
266
267
                if (!isset($tag['name'])) {
268
                    throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
269
                }
270
271
                $name = $tag['name'];
272
                unset($tag['name']);
273
274
                foreach ($tag as $attribute => $value) {
275
                    if (!is_scalar($value) && null !== $value) {
276
                        throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file));
277
                    }
278
                }
279
280
                $definition->addTag($name, $tag);
281
            }
282
        }
283
284 View Code Duplication
        if (isset($service['decorates'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
285
            $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
286
            $definition->setDecoratedService($service['decorates'], $renameId);
0 ignored issues
show
Bug introduced by
It seems like $service['decorates'] can also be of type array; however, Symfony\Component\Depend...::setDecoratedService() does only seem to accept null|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $renameId defined by isset($service['decorati...ion_inner_name'] : null on line 285 can also be of type array; however, Symfony\Component\Depend...::setDecoratedService() does only seem to accept null|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
287
        }
288
289
        $this->container->setDefinition($id, $definition);
290
    }
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
408
    {
409
        foreach ($content as $namespace => $values) {
410
            if (in_array($namespace, array('imports', 'parameters', 'services'))) {
411
                continue;
412
            }
413
414
            if (!is_array($values)) {
415
                $values = array();
416
            }
417
418
            $this->container->loadFromExtension($namespace, $values);
419
        }
420
    }
421
}
422