YamlFileLoader::parseDefinition()   F
last analyzed

Complexity

Conditions 42
Paths > 20000

Size

Total Lines 150
Code Lines 83

Duplication

Lines 85
Ratio 56.67 %

Importance

Changes 0
Metric Value
cc 42
eloc 83
c 0
b 0
f 0
nc 4128772
nop 3
dl 85
loc 150
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @file
5
 * Contains \Drupal\Core\DependencyInjection\YamlFileLoader.
6
 */
7
8
namespace Drupal\Core\DependencyInjection;
9
10
use Drupal\Component\FileCache\FileCacheFactory;
11
use Drupal\Component\Serialization\Yaml;
12
use Symfony\Component\DependencyInjection\Alias;
13
use Symfony\Component\DependencyInjection\ContainerInterface;
14
use Symfony\Component\DependencyInjection\Definition;
15
use Symfony\Component\DependencyInjection\DefinitionDecorator;
16
use Symfony\Component\DependencyInjection\Reference;
17
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18
19
/**
20
 * YamlFileLoader loads YAML files service definitions.
21
 *
22
 * Drupal does not use Symfony's Config component, and Symfony's dependency on
23
 * it cannot be removed easily. Therefore, this is a partial but mostly literal
24
 * copy of upstream, which does not depend on the Config component.
25
 *
26
 * @see \Symfony\Component\DependencyInjection\Loader\YamlFileLoader
27
 * @see https://github.com/symfony/symfony/pull/10920
28
 *
29
 * NOTE: 98% of this code is a literal copy of Symfony's YamlFileLoader.
30
 *
31
 * This file does NOT follow Drupal coding standards, so as to simplify future
32
 * synchronizations.
33
 */
34
class YamlFileLoader
35
{
36
37
    /**
38
     * @var \Drupal\Core\DependencyInjection\ContainerBuilder $container
39
     */
40
    protected $container;
41
42
    /**
43
     * File cache object.
44
     *
45
     * @var \Drupal\Component\FileCache\FileCacheInterface
46
     */
47
    protected $fileCache;
48
49
50
    public function __construct(ContainerBuilder $container)
51
    {
52
        $this->container = $container;
53
        $this->fileCache = FileCacheFactory::get('container_yaml_loader');
54
    }
55
56
    /**
57
     * Loads a Yaml file.
58
     *
59
     * @param mixed  $file The resource
60
     */
61
    public function load($file)
62
    {
63
        // Load from the file cache, fall back to loading the file.
64
        // @todo Refactor this to cache parsed definition objects in
65
        //   https://www.drupal.org/node/2464053
66
        $content = $this->fileCache->get($file);
67
        if (!$content) {
68
            $content = $this->loadFile($file);
69
            $this->fileCache->set($file, $content);
70
        }
71
72
        // Not supported.
73
        //$this->container->addResource(new FileResource($path));
74
75
        // empty file
76
        if (null === $content) {
77
            return;
78
        }
79
80
        // imports
81
        // Not supported.
82
        //$this->parseImports($content, $file);
83
84
        // parameters
85 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...
86
            if (!is_array($content['parameters'])) {
87
                throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $file));
88
            }
89
90
            foreach ($content['parameters'] as $key => $value) {
91
                $this->container->setParameter($key, $this->resolveServices($value));
92
            }
93
        }
94
95
        // extensions
96
        // Not supported.
97
        //$this->loadFromExtensions($content);
98
99
        // services
100
        $this->parseDefinitions($content, $file);
101
    }
102
103
    /**
104
     * Parses definitions
105
     *
106
     * @param array  $content
107
     * @param string $file
108
     */
109 View Code Duplication
    private function parseDefinitions($content, $file)
110
    {
111
        if (!isset($content['services'])) {
112
            return;
113
        }
114
115
        if (!is_array($content['services'])) {
116
            throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
117
        }
118
119
        foreach ($content['services'] as $id => $service) {
120
            $this->parseDefinition($id, $service, $file);
121
        }
122
    }
123
124
    /**
125
     * Parses a definition.
126
     *
127
     * @param string $id
128
     * @param array  $service
129
     * @param string $file
130
     *
131
     * @throws InvalidArgumentException When tags are invalid
132
     */
133
    private function parseDefinition($id, $service, $file)
134
    {
135 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...
136
            $this->container->setAlias($id, substr($service, 1));
137
138
            return;
139
        }
140
141 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...
142
            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));
143
        }
144
145 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...
146
            $public = !array_key_exists('public', $service) || (bool) $service['public'];
147
            $this->container->setAlias($id, new Alias($service['alias'], $public));
148
149
            return;
150
        }
151
152 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...
153
            $definition = new DefinitionDecorator($service['parent']);
154
        } else {
155
            $definition = new Definition();
156
        }
157
158
        if (isset($service['class'])) {
159
            $definition->setClass($service['class']);
160
        }
161
162
        if (isset($service['scope'])) {
163
            $definition->setScope($service['scope']);
164
        }
165
166
        if (isset($service['synthetic'])) {
167
            $definition->setSynthetic($service['synthetic']);
168
        }
169
170
        if (isset($service['synchronized'])) {
171
            $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...
172
        }
173
174
        if (isset($service['lazy'])) {
175
            $definition->setLazy($service['lazy']);
176
        }
177
178
        if (isset($service['public'])) {
179
            $definition->setPublic($service['public']);
180
        }
181
182
        if (isset($service['abstract'])) {
183
            $definition->setAbstract($service['abstract']);
184
        }
185
186 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...
187
            if (is_string($service['factory'])) {
188
                if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) {
189
                    $parts = explode(':', $service['factory']);
190
                    $definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1]));
191
                } else {
192
                    $definition->setFactory($service['factory']);
193
                }
194
            } else {
195
                $definition->setFactory(array($this->resolveServices($service['factory'][0]), $service['factory'][1]));
196
            }
197
        }
198
199
        if (isset($service['factory_class'])) {
200
            $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...
201
        }
202
203
        if (isset($service['factory_method'])) {
204
            $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...
205
        }
206
207
        if (isset($service['factory_service'])) {
208
            $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...
209
        }
210
211
        if (isset($service['file'])) {
212
            $definition->setFile($service['file']);
213
        }
214
215
        if (isset($service['arguments'])) {
216
            $definition->setArguments($this->resolveServices($service['arguments']));
217
        }
218
219
        if (isset($service['properties'])) {
220
            $definition->setProperties($this->resolveServices($service['properties']));
221
        }
222
223 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...
224
            if (is_string($service['configurator'])) {
225
                $definition->setConfigurator($service['configurator']);
226
            } else {
227
                $definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1]));
228
            }
229
        }
230
231 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...
232
            if (!is_array($service['calls'])) {
233
                throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
234
            }
235
236
            foreach ($service['calls'] as $call) {
237
                if (isset($call['method'])) {
238
                    $method = $call['method'];
239
                    $args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
240
                } else {
241
                    $method = $call[0];
242
                    $args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
243
                }
244
245
                $definition->addMethodCall($method, $args);
246
            }
247
        }
248
249 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...
250
            if (!is_array($service['tags'])) {
251
                throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
252
            }
253
254
            foreach ($service['tags'] as $tag) {
255
                if (!is_array($tag)) {
256
                    throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
257
                }
258
259
                if (!isset($tag['name'])) {
260
                    throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
261
                }
262
263
                $name = $tag['name'];
264
                unset($tag['name']);
265
266
                foreach ($tag as $attribute => $value) {
267
                    if (!is_scalar($value) && null !== $value) {
268
                        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));
269
                    }
270
                }
271
272
                $definition->addTag($name, $tag);
273
            }
274
        }
275
276 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...
277
            $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
278
            $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 277 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...
279
        }
280
281
        $this->container->setDefinition($id, $definition);
282
    }
283
284
    /**
285
     * Loads a YAML file.
286
     *
287
     * @param string $file
288
     *
289
     * @return array The file content
290
     *
291
     * @throws InvalidArgumentException when the given file is not a local file or when it does not exist
292
     */
293
    protected function loadFile($file)
294
    {
295
        if (!stream_is_local($file)) {
296
            throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file));
297
        }
298
299
        if (!file_exists($file)) {
300
            throw new InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file));
301
        }
302
303
        return $this->validate(Yaml::decode(file_get_contents($file)), $file);
304
    }
305
306
    /**
307
     * Validates a YAML file.
308
     *
309
     * @param mixed  $content
310
     * @param string $file
311
     *
312
     * @return array
313
     *
314
     * @throws InvalidArgumentException When service file is not valid
315
     */
316
    private function validate($content, $file)
317
    {
318
        if (null === $content) {
319
            return $content;
320
        }
321
322
        if (!is_array($content)) {
323
            throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
324
        }
325
326
        if ($invalid_keys = array_diff_key($content, array('parameters' => 1, 'services' => 1))) {
327
            throw new InvalidArgumentException(sprintf('The service file "%s" is not valid: it contains invalid keys %s. Services have to be added under "services" and Parameters under "parameters".', $file, $invalid_keys));
328
        }
329
330
        return $content;
331
    }
332
333
    /**
334
     * Resolves services.
335
     *
336
     * @param string|array $value
337
     *
338
     * @return array|string|Reference
339
     */
340 View Code Duplication
    private function resolveServices($value)
341
    {
342
        if (is_array($value)) {
343
            $value = array_map(array($this, 'resolveServices'), $value);
344
        } elseif (is_string($value) &&  0 === strpos($value, '@=')) {
345
            // Not supported.
346
            //return new Expression(substr($value, 2));
347
            throw new InvalidArgumentException(sprintf("'%s' is an Expression, but expressions are not supported.", $value));
348
        } elseif (is_string($value) &&  0 === strpos($value, '@')) {
349
            if (0 === strpos($value, '@@')) {
350
                $value = substr($value, 1);
351
                $invalidBehavior = null;
352
            } elseif (0 === strpos($value, '@?')) {
353
                $value = substr($value, 2);
354
                $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
355
            } else {
356
                $value = substr($value, 1);
357
                $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
358
            }
359
360
            if ('=' === substr($value, -1)) {
361
                $value = substr($value, 0, -1);
362
                $strict = false;
363
            } else {
364
                $strict = true;
365
            }
366
367
            if (null !== $invalidBehavior) {
368
                $value = new Reference($value, $invalidBehavior, $strict);
369
            }
370
        }
371
372
        return $value;
373
    }
374
375
}
376