Passed
Branch master (b33026)
by Divine Niiquaye
159:36 queued 107:08
created

YamlFileLoader::parseDefinition()   F

Complexity

Conditions 24
Paths 8964

Size

Total Lines 93
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 24.0368

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 93
ccs 48
cts 50
cp 0.96
rs 0
c 0
b 0
f 0
cc 24
nc 8964
nop 5
crap 24.0368

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
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI\Loader;
19
20
use Rade\DI\Builder\Reference;
21
use Rade\DI\Builder\Statement;
22
use Rade\DI\Config\ConfigurationInterface;
23
use Rade\DI\Container;
24
use Rade\DI\ContainerBuilder;
25
use Rade\DI\Definition;
26
use Symfony\Component\Config\Exception\LoaderLoadException;
27
use Symfony\Component\Config\Resource\FileExistenceResource;
28
use Symfony\Component\Config\Resource\FileResource;
29
use Symfony\Component\Yaml\Exception\ParseException;
30
use Symfony\Component\Yaml\Parser as YamlParser;
31
use Symfony\Component\Yaml\Tag\TaggedValue;
32
use Symfony\Component\Yaml\Yaml;
33
34
/**
35
 * YamlFileLoader loads YAML files service definitions.
36
 *
37
 * @experimental in 1.0
38
 *
39
 * @author Divine Niiquaye Ibok <[email protected]>
40
 */
41
class YamlFileLoader extends FileLoader
42
{
43
    private const DEFAULTS_KEYWORDS = [
44
        'private' => 'private',
45
        'tags' => 'tags',
46
        'autowire' => 'autowire',
47
        'bind' => 'bind',
48
        'calls' => 'bind',
49
    ];
50
51
    private const SERVICE_KEYWORDS = [
52
        'alias' => 'alias',
53
        'entity' => 'entity',
54
        'class' => 'entity', // backward compatibility for symfony devs, will be dropped in 2.0
55
        'arguments' => 'arguments',
56
        'lazy' => 'lazy',
57
        'private' => 'private',
58
        'deprecated' => 'deprecated',
59
        'factory' => 'factory',
60
        'tags' => 'tags',
61
        'decorates' => 'decorates',
62
        'autowire' => 'autowire',
63
        'bind' => 'bind',
64
        'calls' => 'bind',
65
    ];
66
67
    private const PROTOTYPE_KEYWORDS = [
68
        'resource' => 'resource',
69
        'namespace' => 'namespace',
70
        'exclude' => 'exclude',
71
        'lazy' => 'lazy',
72
        'private' => 'private',
73
        'deprecated' => 'deprecated',
74
        'factory' => 'factory',
75
        'tags' => 'tags',
76
        'autowire' => 'autowire',
77
        'arguments' => 'arguments',
78
        'bind' => 'bind',
79
        'calls' => 'bind',
80
    ];
81
82
    /** @var YamlParser */
83
    private $yamlParser;
84
85
    /**
86
     * {@inheritdoc}
87
     */
88 68
    public function load($resource, string $type = null): void
89
    {
90 68
        $path = $this->locator->locate($resource);
91 67
        $content = $this->loadFile($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $file of Rade\DI\Loader\YamlFileLoader::loadFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
        $content = $this->loadFile(/** @scrutinizer ignore-type */ $path);
Loading history...
92
93 66
        if ($this->container instanceof ContainerBuilder) {
94 38
            $this->container->addResource(new FileExistenceResource($path));
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $resource of Symfony\Component\Config...Resource::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

94
            $this->container->addResource(new FileExistenceResource(/** @scrutinizer ignore-type */ $path));
Loading history...
Bug introduced by
The method addResource() does not exist on Rade\DI\AbstractContainer. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

94
            $this->container->/** @scrutinizer ignore-call */ 
95
                              addResource(new FileExistenceResource($path));
Loading history...
95 38
            $this->container->addResource(new FileResource($path));
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $resource of Symfony\Component\Config...Resource::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

95
            $this->container->addResource(new FileResource(/** @scrutinizer ignore-type */ $path));
Loading history...
96
        }
97
98 66
        if (!empty($content)) {
99 66
            $this->loadContent($content, $path);
100
        }
101 44
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 5
    public function supports($resource, string $type = null)
107
    {
108 5
        if (!\is_string($resource)) {
109 2
            return false;
110
        }
111
112 5
        if (null === $type && \in_array(\pathinfo($resource, \PATHINFO_EXTENSION), ['yaml', 'yml'], true)) {
113 5
            return true;
114
        }
115
116 4
        return \in_array($type, ['yaml', 'yml'], true);
117
    }
118
119
    /**
120
     * Loads a YAML file.
121
     *
122
     * @param string $file
123
     *
124
     * @throws \InvalidArgumentException when the given file is not a local file or when it does not exist
125
     *
126
     * @return array The file content
127
     */
128 71
    protected function loadFile($file)
129
    {
130 71
        if (!\class_exists(\Symfony\Component\Yaml\Parser::class)) {
131
            throw new \RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.');
132
        }
133
134 71
        if (!\stream_is_local($file)) {
135
            throw new \InvalidArgumentException(\sprintf('This is not a local file "%s".', $file));
136
        }
137
138 71
        if (!\is_file($file)) {
139 2
            throw new \InvalidArgumentException(\sprintf('The file "%s" does not exist.', $file));
140
        }
141
142 69
        if (null === $this->yamlParser) {
143 69
            $this->yamlParser = new YamlParser();
144
        }
145
146
        try {
147 69
            $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
148 3
        } catch (ParseException $e) {
149 3
            throw new \InvalidArgumentException(\sprintf('The file "%s" does not contain valid YAML: ', $file) . $e->getMessage(), 0, $e);
150
        }
151
152 66
        if (null === $configuration) {
153
            return [];
154
        }
155
156 66
        if (!\is_array($configuration)) {
157
            throw new \InvalidArgumentException(\sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
158
        }
159
160 66
        return $configuration;
161
    }
162
163 66
    private function parseImports(array $content, string $file): void
164
    {
165 66
        if (!isset($content['imports'])) {
166 64
            return;
167
        }
168
169 5
        if (!\is_array($content['imports'])) {
170 1
            throw new \InvalidArgumentException(\sprintf('The "imports" key should contain an array in "%s". Check your YAML syntax.', $file));
171
        }
172
173 4
        $defaultDirectory = \dirname($file);
174
175 4
        foreach ($content['imports'] as $import) {
176 4
            if (!\is_array($import)) {
177 1
                $import = ['resource' => $import];
178
            }
179
180 4
            if (!isset($import['resource'])) {
181 1
                throw new \InvalidArgumentException(\sprintf('An import should provide a resource in "%s". Check your YAML syntax.', $file));
182
            }
183
184 3
            if ('not_found' === $ignoreErrors = $import['ignore_errors'] ?? false) {
185 1
                $notFound = $ignoreErrors = false; // Ignore error on missing file resource.
186
            }
187
188 3
            $this->setCurrentDir($defaultDirectory);
189
190
            try {
191 3
                $this->import($import['resource'], $import['type'] ?? null, $ignoreErrors, $file);
192 1
            } catch (LoaderLoadException $e) {
193 1
                if (!isset($notFound)) {
194 1
                    throw $e;
195
                }
196
            }
197
        }
198
199 3
        unset($content['imports']);
200 3
    }
201
202 66
    private function loadContent($content, $path): void
203
    {
204
        // imports
205 66
        $this->parseImports($content, $path);
206
207
        // parameters
208 64
        if (isset($content['parameters'])) {
209 8
            if (!\is_array($content['parameters'])) {
210 1
                throw new \InvalidArgumentException(\sprintf('The "parameters" key should contain an array in "%s". Check your YAML syntax.', $path));
211
            }
212
213 7
            foreach ($content['parameters'] as $key => $value) {
214 7
                $this->container->parameters[$key] = $this->resolveServices($value, $path, true);
215
            }
216
217 6
            unset($content['parameters']);
218
        }
219
220
        // service providers
221 62
        $this->loadServiceProviders($content, $path);
222
223 62
        $this->setCurrentDir(\dirname($path));
224
225
        // load definitions
226 62
        $this->parseDefinitions($content, $path);
227 44
    }
228
229
    /**
230
     * Loads Service Providers.
231
     */
232 62
    private function loadServiceProviders(array $content, string $path): void
233
    {
234 62
        foreach ($content['service_providers'] ?? [] as $k => $provider) {
235 4
            if (\is_string($k)) {
236
                throw new \InvalidArgumentException(\sprintf('Invalid service provider key %s, only list sequence is supported "service_providers: ..." in "%s".', $k, $path));
237
            }
238
239 4
            if ($provider instanceof TaggedValue && 'provider' === $provider->getTag()) {
240
                $value = $provider->getValue();
241
242
                $provider = $value['id'] ?? null;
243
                $args = $this->resolveServices($value['args'] ?? [], $path);
244
                $config = $this->resolveServices($value['config'] ?? [], $path);
245 4
            } elseif (\is_array($provider)) {
246 2
                $provider = \key($value = $provider);
247 2
                $config = $this->resolveServices($value[$provider] ?? [], $path);
248
            }
249
250 4
            if (!\is_string($provider)) {
251
                continue;
252
            }
253
254 4
            if ($this->container instanceof Container) {
255 2
                $extension = $this->container->resolveClass($provider, $args ?? []);
256
            } else {
257 2
                $extension = (new \ReflectionClass($provider))->newInstanceArgs($args ?? []);
258
            }
259
260 4
            if (!\is_array($config ?? null)) {
261 2
                $config = $extension instanceof ConfigurationInterface ? (array) $content[$extension->getId()] ?? [] : [];
262
            }
263
264 4
            $this->container->register($extension, $config ?? []);
265
        }
266
267 62
        unset($content['service_providers']);
268 62
    }
269
270
    /**
271
     * Resolves services.
272
     *
273
     * @return array|string|Reference|Statement|object|null
274
     */
275 56
    private function resolveServices($value, string $file, bool $isParameter = false)
276
    {
277 56
        if ($value instanceof TaggedValue) {
278 4
            if ($isParameter) {
279
                throw new \InvalidArgumentException(\sprintf('Using tag "!%s" in a parameter is not allowed in "%s".', $value->getTag(), $file));
280
            }
281
282 4
            $argument = $value->getValue();
283
284 4
            if ('reference' === $value->getTag()) {
285
                if (!\is_string($argument)) {
286
                    throw new \InvalidArgumentException(\sprintf('"!reference" tag only accepts string value in "%s".', $file));
287
                }
288
289
                if (!$this->container->has($argument)) {
290
                    throw new \InvalidArgumentException(\sprintf('Creating an alias using the tag "!reference" is not allowed in "%s".', $file));
291
                }
292
293
                return $this->container instanceof Container ? $this->container->get($argument) : new Reference($argument);
294
            }
295
296 4
            if ('tagged' === $value->getTag()) {
297 2
                if (\is_string($argument) && '' !== $argument) {
298
                    return $this->container->tagged($argument);
299
                }
300
301 2
                if (\is_array($argument) && (isset($argument['tag']) && '' !== $argument['tag'])) {
302 2
                    return $this->container->tagged($argument['tag'], $argument['resolve'] ?? true);
303
                }
304
305
                throw new \InvalidArgumentException(\sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file));
306
            }
307
308 2
            if ('statement' === $value->getTag()) {
309 2
                if (\is_string($argument)) {
310 2
                    return $this->container instanceof Container ? $this->container->call($argument) : new Statement($argument);
311
                }
312
313 2
                if (!\is_array($argument)) {
314
                    throw new \InvalidArgumentException(\sprintf('"!statement" tag only accepts sequences in "%s".', $file));
315
                }
316
317 2
                if (\array_keys($argument) !== ['value', 'args']) {
318
                    throw new \InvalidArgumentException('"!statement" tag only accepts array keys of "value" and "args"');
319
                }
320
321 2
                $argument = $this->resolveServices($argument, $file, $isParameter);
322
323 2
                if ($this->container instanceof Container) {
324 1
                    return $this->container->call($argument['value'], $argument['args']);
325
                }
326
327 1
                return new Statement($argument['value'], $argument['args']);
328
            }
329
330
            throw new \InvalidArgumentException(\sprintf('Unsupported tag "!%s".', $value->getTag()));
331
        }
332
333 56
        if (\is_array($value)) {
334 53
            foreach ($value as $k => $v) {
335 51
                $value[$k] = $this->resolveServices($v, $file, $isParameter);
336
            }
337 56
        } elseif (\is_string($value) && '@' === $value[0]) {
338 10
            $value = \substr($value, 1);
339
340
            // double @@ should be escaped
341 10
            if ('@' === $value[0]) {
342 3
                return $value;
343
            }
344
345
            // ignore on invalid reference
346 10
            if ('?' === $value[0]) {
347
                $value = \substr($value, 1);
348
349
                if (!$this->container->has($value)) {
350
                    return null;
351
                }
352
            }
353
354 10
            return $this->container instanceof Container ? $this->container->get($value) : new Reference($value);
355
        }
356
357 56
        if (!\is_string($value)) {
358 53
            return $value;
359
        }
360
361 52
        return $this->resolveParameters($value);
362
    }
363
364 62
    private function parseDefinitions(array $content, string $file): void
365
    {
366 62
        if (!isset($content['services'])) {
367 8
            return;
368
        }
369
370 56
        if (!\is_array($content['services'])) {
371 1
            throw new \InvalidArgumentException(\sprintf('The "services" key should contain an array in "%s". Check your YAML syntax.', $file));
372
        }
373
374 55
        $defaults = $this->parseDefaults($content, $file);
375
376 53
        foreach ($content['services'] as $id => $service) {
377 53
            if (\preg_match('/^_[a-zA-Z0-9_]*$/', $id)) {
378 2
                throw new \InvalidArgumentException(\sprintf('Service names that start with an underscore are reserved. Rename the "%s" service.', $id));
379
            }
380
381 51
            $service = $this->resolveServices($service, $file);
382
383 51
            if ($service instanceof Reference) {
384 2
                $this->container->alias($id, (string) $service);
385
386 2
                continue;
387
            }
388
389 51
            if ($service instanceof Statement) {
390 1
                $this->container->autowire($id, $service);
0 ignored issues
show
Bug introduced by
$service of type Rade\DI\Builder\Statement is incompatible with the type array expected by parameter $types of Rade\DI\AbstractContainer::autowire(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

390
                $this->container->autowire($id, /** @scrutinizer ignore-type */ $service);
Loading history...
391
392 1
                continue;
393
            }
394
395 50
            if (\is_object($service)) {
396 3
                $this->container->set($id, $service, true);
0 ignored issues
show
Bug introduced by
The method set() does not exist on Rade\DI\AbstractContainer. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

396
                $this->container->/** @scrutinizer ignore-call */ 
397
                                  set($id, $service, true);
Loading history...
397
398 3
                continue;
399
            }
400
401 49
            if (empty($service)) {
402 4
                if ([] === $defaults) {
403
                    continue;
404
                }
405
406 4
                $service = []; // If $defaults, then a definition creation should be possible.
407
            }
408
409 49
            if (!\is_array($service)) {
410 1
                throw new \InvalidArgumentException(\sprintf('A service definition must be an array, a tagged "!statement" or a string starting with "@", but "%s" found for service "%s" in "%s". Check your YAML syntax.', \get_debug_type($service), $id, $file));
411
            }
412
413 48
            if ($this->container->has($id) && $this->container instanceof Container) {
414 2
                $this->container->extend($id, function (Definition $definition) use ($id, $service, $file, $defaults): Definition {
0 ignored issues
show
Bug introduced by
The method extend() does not exist on Rade\DI\AbstractContainer. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

414
                $this->container->/** @scrutinizer ignore-call */ 
415
                                  extend($id, function (Definition $definition) use ($id, $service, $file, $defaults): Definition {
Loading history...
415 2
                    $this->parseDefinition($id, $service, $file, $defaults, $definition);
416
417 2
                    return $definition;
418 2
                });
419
420 2
                continue;
421
            }
422
423 47
            $this->parseDefinition($id, $service, $file, $defaults);
424
        }
425 38
    }
426
427
    /**
428
     * @throws \InvalidArgumentException
429
     */
430 55
    private function parseDefaults(array &$content, string $file): array
431
    {
432 55
        if (!\array_key_exists('_defaults', $content['services'])) {
433 39
            return [];
434
        }
435 16
        $defaults = $content['services']['_defaults'];
436 16
        unset($content['services']['_defaults']);
437
438 16
        if (!\is_array($defaults)) {
439 2
            throw new \InvalidArgumentException(\sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', \get_debug_type($defaults), $file));
440
        }
441
442 14
        foreach ($defaults as $key => $default) {
443 14
            if (!isset(self::DEFAULTS_KEYWORDS[$key])) {
444
                throw new \InvalidArgumentException(\sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, \implode('", "', self::DEFAULTS_KEYWORDS)));
445
            }
446
        }
447
448 14
        if (isset($defaults['tags'])) {
449 4
            if (!\is_array($tags = $defaults['tags'])) {
450
                throw new \InvalidArgumentException(\sprintf('Parameter "tags" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
451
            }
452
453 4
            $defaults['tags'] = $this->parseDefinitionTags('in "_defaults"', $tags, $file);
454
        }
455
456 14
        if (null !== $bindings = $defaults['bind'] ?? $default['calls'] ?? null) {
457 4
            if (!\is_array($bindings)) {
458
                throw new \InvalidArgumentException(\sprintf('Parameter "bind" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file));
459
            }
460
461 4
            unset($default['calls']); // To avoid conflicts, will be dropped in 1.3
462
463 4
            $defaults['bind'] = $this->parseDefinitionBinds('in "_defaults"', $bindings, $file);
464
        }
465
466 14
        return $defaults;
467
    }
468
469
    /**
470
     * Parses a definition.
471
     *
472
     * @throws \InvalidArgumentException
473
     */
474 48
    private function parseDefinition(string $id, array $service, string $file, array $defaults, Definition $definition = null): void
475
    {
476 48
        $this->checkDefinition($id, $service, $file);
477
478 48
        if ($this->container->has($id) && $this->container instanceof ContainerBuilder) {
479 2
            $definition = \array_key_exists($id, $this->container->keys()) ? $this->container->extend($id) : null;
480
        }
481
482
        // Non existing entity
483 48
        if (!isset($service['entity'])) {
484 25
            $service['entity'] = $service['class'] ?? (\class_exists($id) ? $id : null);
485
        }
486
487 48
        $arguments = $this->resolveServices($service['arguments'] ?? [], $file);
488
489 48
        if ($definition instanceof Definition) {
490 2
            $hasDefinition = true;
491
492 2
            $definition->replace($service['entity'], null !== $service['entity'])
493 2
                ->args(\array_merge($definition->get('parameters'), $arguments));
494
        } else {
495 47
            $definition = new Definition($service['entity'], $arguments);
496
        }
497
498 48
        if ($this->container instanceof ContainerBuilder) {
499 25
            $definition->should(Definition::PRIVATE, $service['private'] ?? $defaults['private'] ?? false);
500
        }
501
502 48
        $definition->should(Definition::LAZY, $service['lazy'] ?? false);
503 48
        $definition->should(Definition::FACTORY, $service['factory'] ?? false);
504 48
        $this->autowired[$id] = $autowired = $service['autowire'] ?? $defaults['autowire'] ?? false;
505
506 48
        if (isset($service['deprecated'])) {
507 2
            $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']];
508 2
            $deprecation = [$deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? null];
509
        }
510
511 48
        if (!\is_array($bindings = $service['bind'] ?? $service['calls'] ?? [])) {
512
            throw new \InvalidArgumentException(\sprintf('Parameter "bind" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
513
        }
514 48
        $bindings = \array_merge($defaults['bind'] ?? [], $bindings);
515
516 48
        if ([] !== $bindings) {
517 12
            $this->parseDefinitionBinds($id, $bindings, $file, $definition);
518
        }
519
520 48
        if (!\is_array($tags = $service['tags'] ?? [])) {
521 4
            throw new \InvalidArgumentException(\sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file));
522
        }
523
524 44
        if (isset($defaults['tags'])) {
525 2
            $tags = \array_merge($defaults['tags'], $tags);
526
        }
527
528 44
        if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) {
529 2
            throw new \InvalidArgumentException(\sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file));
530
        }
531
532 42
        if (\array_key_exists('resource', $service)) {
533 8
            if (!\is_string($service['resource'])) {
534
                throw new \InvalidArgumentException(\sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file));
535
            }
536
537 8
            $namespace = $service['namespace'] ?? $id;
538
539 8
            $this->autowired[$namespace] = $autowired;
540 8
            $this->deprecations[$namespace] = $deprecation ?? null;
541
542 8
            if ([] !== $tags) {
543 2
                $this->tags[$namespace] = $this->parseDefinitionTags($id, $tags, $file);
544
            }
545
546 8
            $this->registerClasses($definition, $namespace, $service['resource'], $service['exclude'] ?? []);
547
548 8
            return;
549
        }
550
551 34
        if (!isset($hasDefinition)) {
552 33
            $definition = $this->container->set($id, $definition);
553
        }
554
555 34
        if (false !== $autowired) {
556 8
            $definition->autowire(\is_array($autowired) ? $autowired : []);
557
        }
558
559 34
        if (isset($deprecation)) {
560 2
            [$package, $version, $message] = $deprecation;
561
562 2
            $definition->deprecate($package, $version, $message);
563
        }
564
565 34
        if ([] !== $tags) {
566 11
            $this->container->tag($id, $this->parseDefinitionTags($id, $tags, $file));
567
        }
568 30
    }
569
570
    /**
571
     * @param array<int,string[]> $bindings
572
     *
573
     * @return array<int,string[]>
574
     */
575 12
    private function parseDefinitionBinds(string $id, array $bindings, string $file, Definition $definition = null): array
576
    {
577 12
        if ('in "_defaults"' !== $id) {
578 12
            $id = \sprintf('for service "%s"', $id);
579
        }
580
581 12
        foreach ($bindings as $k => $call) {
582 12
            if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) {
583
                throw new \InvalidArgumentException(\sprintf('Invalid bind call %s: expected map or array, "%s" given in "%s".', $id, $call instanceof TaggedValue ? '!' . $call->getTag() : \get_debug_type($call), $file));
584
            }
585
586 12
            if (\is_string($k)) {
587
                throw new \InvalidArgumentException(\sprintf('Invalid bind call %s, did you forgot a leading dash before "%s: ..." in "%s"?', $id, $k, $file));
588
            }
589
590 12
            if (empty($call)) {
591
                throw new \InvalidArgumentException(\sprintf('Invalid call %s: the bind must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file));
592
            }
593
594 12
            if (1 === \count($call) && \is_string(\key($call))) {
595 9
                $method = \key($call);
596 9
                $args = $this->resolveServices($call[$method], $file);
597
            } elseif (empty($call)) {
598
                throw new \InvalidArgumentException(\sprintf('Invalid call %s: the bind must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file));
599
            } else {
600 10
                $method = $call[0];
601 10
                $args = $this->resolveServices($call[1] ?? null, $file);
602
            }
603
604 12
            if ($definition instanceof Definition) {
605 12
                $definition->bind($method, $args);
606
607 12
                continue;
608
            }
609
610 4
            $bindings[$k] = [$method, $args];
611
        }
612
613 12
        return $bindings;
614
    }
615
616 15
    private function parseDefinitionTags(string $id, array $tags, string $file): array
617
    {
618 15
        if ('in "_defaults"' !== $id) {
619 13
            $id = \sprintf('for service "%s"', $id);
620
        }
621
622 15
        $serviceTags = [];
623
624 15
        foreach ($tags as $k => $tag) {
625 15
            if (\is_string($k)) {
626 2
                throw new \InvalidArgumentException(\sprintf('The "tags" entry %s is invalid, did you forgot a leading dash before "%s: ..." in "%s"?', $id, $k, $file));
627
            }
628
629 13
            if (\is_string($tag)) {
630 8
                $serviceTags[] = $tag;
631
632 8
                continue;
633
            }
634
635 5
            foreach ((array) $tag as $name => $value) {
636 5
                if (!\is_string($name) || '' === $name) {
637 2
                    throw new \InvalidArgumentException(\sprintf('The tag name %s in "%s" must be a non-empty string. Check your YAML syntax.', $id, $file));
638
                }
639
640 3
                $serviceTags[$name] = $value;
641
            }
642
        }
643
644 11
        return $serviceTags;
645
    }
646
647
    /**
648
     * Checks the keywords used to define a service.
649
     */
650 48
    private function checkDefinition(string $id, array $definition, string $file): void
651
    {
652 48
        if (isset($definition['resource']) || isset($definition['namespace'])) {
653 10
            $keywords = self::PROTOTYPE_KEYWORDS;
654
        } else {
655 38
            $keywords = self::SERVICE_KEYWORDS;
656
        }
657
658 48
        foreach ($definition as $key => $value) {
659 46
            if (!isset($keywords[$key])) {
660 2
                throw new \InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, \implode('", "', $keywords)));
661
            }
662
        }
663 48
    }
664
}
665