ServiceDefinition::processMethod()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 2
crap 2
1
<?php
2
3
namespace BrainExe\Core\Annotations\Builder;
4
5
use BrainExe\Core\Annotations\Inject;
6
use BrainExe\Core\Annotations\Service;
7
use Doctrine\Common\Annotations\Reader;
8
use InvalidArgumentException;
9
use ReflectionClass;
10
use ReflectionMethod;
11
use Symfony\Component\DependencyInjection\ContainerBuilder;
12
use Symfony\Component\DependencyInjection\ContainerInterface;
13
use Symfony\Component\DependencyInjection\Definition;
14
use Symfony\Component\DependencyInjection\Reference;
15
use Symfony\Component\ExpressionLanguage\Expression;
16
17
/**
18
 * @todo matze complexity
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
19
 */
20
class ServiceDefinition
21
{
22
    /**
23
     * @var Reader
24
     */
25
    protected $reader;
26
27
    /**
28
     * @var ContainerBuilder
29
     */
30
    protected $container;
31
32
    /**
33
     * @param ContainerBuilder $container
34
     * @param Reader $reader
35
     */
36 1
    public function __construct(ContainerBuilder $container, Reader $reader)
37
    {
38 1
        $this->reader = $reader;
39 1
        $this->container = $container;
40 1
    }
41
42
    /**
43
     * @param ReflectionClass $reflectionClass
44
     * @param Service $annotation
45
     * @param Definition $definition
46
     * @return array
47
     */
48 1
    public function build(ReflectionClass $reflectionClass, Service $annotation, Definition $definition)
49
    {
50 1
        $constructor = $reflectionClass->getConstructor();
51 1
        if (null !== $constructor) {
52 1
            $this->processConstructor($constructor, $definition);
53
        }
54
55 1
        $this->processService($annotation, $definition);
56 1
        $this->processMethods($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC), $definition);
57 1
        $this->processTags($reflectionClass, $annotation, $definition);
58
59 1
        $serviceId = $annotation->value ?: $annotation->name ?: $reflectionClass->getName();
60
61 1
        return [$this->setupDefinition($definition, $serviceId) ?? $serviceId, $definition];
62
    }
63
64
    /**
65
     * @param Definition $definition
66
     * @param string $serviceId
67
     * @return string null
68
     */
69 1
    public function setupDefinition(Definition $definition, string $serviceId)
70
    {
71 1
        return null;
72
    }
73
74
    /**
75
     * @param string|string[] $value
76
     * @return Reference[]|Reference|Expression
77
     */
78 1
    private function resolveServices($value)
79
    {
80 1
        if (is_array($value)) {
81
            return array_map([$this, 'resolveServices'], $value);
82 1
        } elseif ('=' === $value[0]) {
83 1
            return new Expression(mb_substr($value, 1));
84 1
        } elseif ('@' === $value[0]) {
85 1
            return $this->getValueReference($value);
86
        }
87
88 1
        return $value;
89
    }
90
91
    /**
92
     * @param ReflectionMethod $constructor
93
     * @param Definition $definition
94
     */
95 1
    protected function processConstructor(ReflectionMethod $constructor, Definition $definition)
96
    {
97 1
        if ($annotation = $this->reader->getMethodAnnotation($constructor, Inject::class)) {
98
            /** @var Inject $annotation */
99 1
            $arguments = $this->extractArguments(
100 1
                $constructor,
101 1
                $annotation,
102 1
                $definition->getArguments()
103
            );
104
105 1
            $definition->setArguments($arguments);
106 1
        } elseif ($constructor->getNumberOfParameters() > 0) {
107 1
            $definition->setArguments(
108 1
                $this->resolveArguments($constructor)
109
            );
110
111 1
            $definition->setAutowired(true);
112
        }
113 1
    }
114
115
    /**
116
     * @param ReflectionMethod[] $methods
117
     * @param Definition $definition
118
     */
119 1
    protected function processMethods(array $methods, Definition $definition) : void
120
    {
121 1
        foreach ($methods as $method) {
122 1
            if ($method->isConstructor()) {
123 1
                continue;
124
            }
125 1
            $this->processMethod($definition, $method);
126
        }
127 1
    }
128
129
    /**
130
     * @param ReflectionMethod $method
131
     * @param Inject $annotation
132
     * @param array $arguments
133
     * @return array
134
     */
135 1
    private function extractArguments(
136
        ReflectionMethod $method,
137
        Inject $annotation,
138
        array $arguments = []
139
    ) : array {
140 1
        $values = [];
141 1
        if (is_string($annotation->value)) {
142 1
            $values = [$annotation->value];
143 1
        } elseif (is_array($annotation->value)) {
144 1
            $values = $annotation->value;
145
        }
146
147 1
        return $this->resolveArguments($method, $values, $arguments);
148
    }
149
150
    /**
151
     * @param ReflectionMethod $method
152
     * @param array $values
153
     * @param array $arguments
154
     * @return array
155
     */
156 1
    private function resolveArguments(
157
        ReflectionMethod $method,
158
        array $values = [],
159
        array $arguments = []
160
    ) : array {
161 1
        foreach ($method->getParameters() as $index => $parameter) {
162 1
            $name = $parameter->getName();
163 1
            if (!empty($values[$name])) {
164 1
                $arguments[$index] = $this->resolveServices($values[$name]);
165 1
            } elseif (isset($values[$index])) {
166 1
                $arguments[$index] = $this->resolveServices($values[$index]);
167 1
            } elseif (!isset($arguments[$index])) {
168 1
                $parameterClass = $parameter->getClass();
169 1
                if ($parameterClass) {
170 1
                    $arguments[$index] = new Reference($parameterClass->getName());
171
                }
172
            }
173
        }
174
175 1
        return $arguments;
176
    }
177
178
    /**
179
     * @param Service $annotation
180
     * @param Definition $definition
181
     */
182 1
    private function processService(Service $annotation, Definition $definition) : void
183
    {
184 1
        $definition->setAutowired(true);
185 1
        $definition->setPublic($annotation->public);
186 1
        $definition->setLazy($annotation->lazy);
187 1
        $definition->setShared($annotation->shared);
188 1
        $definition->setSynthetic($annotation->synthetic);
189 1
        $definition->setAbstract($annotation->abstract);
190 1
        $this->processConfigurator($annotation, $definition);
191
192 1
        if (null === $annotation->factory) {
193 1
            $definition->setFactory($annotation->factory);
194
        }
195 1
    }
196
197
    /**
198
     * @param ReflectionClass $reflectionClass
199
     * @param Service $annotation
200
     * @param Definition $definition
201
     */
202 1
    private function processTags(
203
        ReflectionClass $reflectionClass,
204
        Service $annotation,
205
        Definition $definition
206
    ) {
207 1
        if (empty($annotation->tags)) {
208 1
            return;
209
        }
210
211 1
        foreach ($annotation->tags as $tag) {
212 1
            if (!isset($tag['name'])) {
213
                throw new InvalidArgumentException(
214
                    sprintf(
215
                        'A "tags" entry is missing a "name" key must be an array for class "%s" in %s.',
216
                        $reflectionClass->getName(),
217
                        $reflectionClass->getFileName()
218
                    )
219
                );
220
            }
221 1
            $name = $tag['name'];
222 1
            unset($tag['name']);
223
224 1
            $definition->addTag($name, $tag);
225
        }
226 1
    }
227
228
    /**
229
     * @param string $value
230
     * @return Reference
231
     */
232 1
    private function getValueReference(string $value) : Reference
233
    {
234 1
        if (0 === strpos($value, '@?')) {
235
            $value           = substr($value, 2);
236
            $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
237
        } else {
238 1
            $value           = substr($value, 1);
239 1
            $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
240
        }
241
242
        // mark reference as strict
243 1
        if ('=' === $value[-1]) {
244
            $value  = substr($value, 0, -1);
245
            $strict = false;
246
        } else {
247 1
            $strict = true;
248
        }
249
250 1
        return new Reference($value, $invalidBehavior, $strict);
0 ignored issues
show
Unused Code introduced by
The call to Reference::__construct() has too many arguments starting with $strict.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
251
    }
252
253
    /**
254
     * @param Service $annotation
255
     * @param Definition $definition
256
     */
257 1
    private function processConfigurator(Service $annotation, Definition $definition) : void
258
    {
259 1
        if (null === $annotation->configurator) {
260 1
            return;
261
        }
262
263
        if (is_string($annotation->configurator)) {
264
            $definition->setConfigurator($annotation->configurator);
265
        } else {
266
            $definition->setConfigurator([
267
                $this->resolveServices($annotation->configurator[0]),
268
                $annotation->configurator[1]
269
            ]);
270
        }
271
    }
272
273
    /**
274
     * @param Definition $definition
275
     * @param ReflectionMethod $method
276
     */
277 1
    protected function processMethod(Definition $definition, ReflectionMethod $method) : void
278
    {
279
        /** @var Inject $annotation */
280 1
        $annotation = $this->reader->getMethodAnnotation($method, Inject::class);
281 1
        if ($annotation) {
282 1
            $arguments = $this->extractArguments(
283 1
                $method,
284 1
                $annotation
285
            );
286 1
            $definition->addMethodCall($method->getName(), $arguments);
287
        }
288 1
    }
289
}
290