RegisterServiceSubscribersPass::processValue()   F
last analyzed

Complexity

Conditions 34
Paths 3585

Size

Total Lines 105
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 105
rs 0
c 0
b 0
f 0
cc 34
nc 3585
nop 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
 * 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\Compiler;
13
14
use Psr\Container\ContainerInterface as PsrContainerInterface;
15
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
16
use Symfony\Component\DependencyInjection\Attribute\Autowire;
17
use Symfony\Component\DependencyInjection\ContainerInterface;
18
use Symfony\Component\DependencyInjection\Definition;
19
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
20
use Symfony\Component\DependencyInjection\Reference;
21
use Symfony\Component\DependencyInjection\TypedReference;
22
use Symfony\Contracts\Service\Attribute\SubscribedService;
23
use Symfony\Contracts\Service\ServiceProviderInterface;
24
use Symfony\Contracts\Service\ServiceSubscriberInterface;
25
26
/**
27
 * Compiler pass to register tagged services that require a service locator.
28
 *
29
 * @author Nicolas Grekas <[email protected]>
30
 */
31
class RegisterServiceSubscribersPass extends AbstractRecursivePass
32
{
33
    protected bool $skipScalars = true;
34
35
    protected function processValue(mixed $value, bool $isRoot = false): mixed
36
    {
37
        if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) {
38
            return parent::processValue($value, $isRoot);
39
        }
40
41
        $serviceMap = [];
42
        $autowire = $value->isAutowired();
43
44
        foreach ($value->getTag('container.service_subscriber') as $attributes) {
45
            if (!$attributes) {
46
                $autowire = true;
47
                continue;
48
            }
49
            ksort($attributes);
50
            if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) {
51
                throw new InvalidArgumentException(\sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $this->currentId));
52
            }
53
            if (!\array_key_exists('id', $attributes)) {
54
                throw new InvalidArgumentException(\sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId));
55
            }
56
            if (!\array_key_exists('key', $attributes)) {
57
                $attributes['key'] = $attributes['id'];
58
            }
59
            if (isset($serviceMap[$attributes['key']])) {
60
                continue;
61
            }
62
            $serviceMap[$attributes['key']] = new Reference($attributes['id']);
63
        }
64
        $class = $value->getClass();
65
66
        if (!$r = $this->container->getReflectionClass($class)) {
0 ignored issues
show
Bug introduced by
The method getReflectionClass() does not exist on null. ( Ignorable by Annotation )

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

66
        if (!$r = $this->container->/** @scrutinizer ignore-call */ getReflectionClass($class)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
67
            throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId));
68
        }
69
        if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) {
70
            throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
71
        }
72
        $class = $r->name;
73
        $subscriberMap = [];
74
75
        foreach ($class::getSubscribedServices() as $key => $type) {
76
            $attributes = [];
77
78
            if (!isset($serviceMap[$key]) && $type instanceof Autowire) {
79
                $subscriberMap[$key] = $type;
80
                continue;
81
            }
82
83
            if ($type instanceof SubscribedService) {
84
                $key = $type->key ?? $key;
85
                $attributes = $type->attributes;
86
                $type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(\sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class)));
87
            }
88
89
            if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) {
90
                throw new InvalidArgumentException(\sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type)));
91
            }
92
            $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
93
            if ('?' === $type[0]) {
94
                $type = substr($type, 1);
95
                $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
96
            }
97
            if (\is_int($name = $key)) {
98
                $key = $type;
99
                $name = null;
100
            }
101
            if (!isset($serviceMap[$key])) {
102
                if (!$autowire) {
103
                    throw new InvalidArgumentException(\sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class));
104
                }
105
                $serviceMap[$key] = new Reference($type);
106
            }
107
108
            if ($name) {
109
                if (false !== $i = strpos($name, '::get')) {
110
                    $name = lcfirst(substr($name, 5 + $i));
111
                } elseif (str_contains($name, '::')) {
112
                    $name = null;
113
                }
114
            }
115
116
            if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) {
117
                $camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));
118
                $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
119
            }
120
121
            $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes);
122
            unset($serviceMap[$key]);
123
        }
124
125
        if ($serviceMap = array_keys($serviceMap)) {
126
            $message = \sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', str_replace('%', '%%', implode('", "', $serviceMap)));
127
            throw new InvalidArgumentException(\sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId));
128
        }
129
130
        $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId);
0 ignored issues
show
Bug introduced by
It seems like $this->container can also be of type null; however, parameter $container of Symfony\Component\Depend...atorTagPass::register() does only seem to accept Symfony\Component\Depend...ection\ContainerBuilder, 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

130
        $locatorRef = ServiceLocatorTagPass::register(/** @scrutinizer ignore-type */ $this->container, $subscriberMap, $this->currentId);
Loading history...
131
132
        $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]);
133
134
        $value->setBindings([
135
            PsrContainerInterface::class => new BoundArgument($locatorRef, false),
136
            ServiceProviderInterface::class => new BoundArgument($locatorRef, false),
137
        ] + $value->getBindings());
138
139
        return parent::processValue($value);
140
    }
141
}
142