Completed
Push — master ( c70c47...2aa4c0 )
by Matze
05:58
created

ServiceDefinition::processService()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.003

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 10
cts 11
cp 0.9091
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 2
crap 2.003
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
 * @author Matthias Dötsch <[email protected]>
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
     * @return string null
67
     */
68 1
    public function setupDefinition(Definition $definition, string $serviceId)
69
    {
70 1
        return null;
71
    }
72
73
    /**
74
     * @param string|string[] $value
75
     * @return Reference[]|Reference|Expression
76
     */
77 1
    private function resolveServices($value)
78
    {
79 1
        if (is_array($value)) {
80
            return array_map([$this, 'resolveServices'], $value);
81 1
        } elseif ('=' === $value[0]) {
82 1
            return new Expression(mb_substr($value, 1));
83 1
        } elseif ('@' === $value[0]) {
84 1
            return $this->getValueReference($value);
85
        }
86
87 1
        return $value;
88
    }
89
90
    /**
91
     * @param ReflectionMethod $constructor
92
     * @param Definition $definition
93
     */
94 1
    protected function processConstructor(ReflectionMethod $constructor, Definition $definition)
95
    {
96 1
        if ($annotation = $this->reader->getMethodAnnotation($constructor, Inject::class)) {
97
            /** @var Inject $annotation */
98 1
            $arguments = $this->extractArguments(
99
                $constructor,
100
                $annotation,
101 1
                $definition->getArguments()
102
            );
103
104 1
            $definition->setArguments($arguments);
105 1
        } elseif ($constructor->getNumberOfParameters() > 0) {
106 1
            $definition->setArguments(
107 1
                $this->resolveArguments($constructor)
108
            );
109
110 1
            $definition->setAutowired(true);
111
        }
112 1
    }
113
114
    /**
115
     * @param ReflectionMethod[] $methods
116
     * @param Definition $definition
117
     */
118 1
    protected function processMethods(array $methods, Definition $definition)
119
    {
120 1
        foreach ($methods as $method) {
121 1
            if ($method->isConstructor()) {
122 1
                continue;
123
            }
124 1
            $this->processMethod($definition, $method);
125
        }
126 1
    }
127
128
    /**
129
     * @param ReflectionMethod $method
130
     * @param Inject $annotation
131
     * @param array $arguments
132
     * @return array
133
     */
134 1
    private function extractArguments(
135
        ReflectionMethod $method,
136
        Inject $annotation,
137
        array $arguments = []
138
    ) : array {
139 1
        $values = [];
140 1
        if (is_string($annotation->value)) {
141 1
            $values = [$annotation->value];
142 1
        } elseif (is_array($annotation->value)) {
143 1
            $values = $annotation->value;
144
        }
145
146 1
        return $this->resolveArguments($method, $values, $arguments);
147
    }
148
149
    /**
150
     * @param ReflectionMethod $method
151
     * @param array $values
152
     * @param array $arguments
153
     * @return array
154
     */
155 1
    private function resolveArguments(
156
        ReflectionMethod $method,
157
        array $values = [],
158
        array $arguments = []
159
    ) : array {
160 1
        foreach ($method->getParameters() as $index => $parameter) {
161 1
            $name = $parameter->getName();
162 1
            if (!empty($values[$name])) {
163 1
                $arguments[$index] = $this->resolveServices($values[$name]);
164 1
            } elseif (isset($values[$index])) {
165 1
                $arguments[$index] = $this->resolveServices($values[$index]);
166 1
            } elseif (!isset($arguments[$index])) {
167 1
                $parameterClass = $parameter->getClass();
168 1
                if ($parameterClass) {
169 1
                    $arguments[$index] = new Reference($parameterClass->getName());
170
                }
171
            }
172
        }
173
174 1
        return $arguments;
175
    }
176
177
    /**
178
     * @param Service $annotation
179
     * @param Definition $definition
180
     */
181 1
    private function processService(Service $annotation, Definition $definition)
182
    {
183 1
        $definition->setAutowired(true);
184 1
        $definition->setPublic($annotation->public);
185 1
        $definition->setLazy($annotation->lazy);
186 1
        $definition->setShared($annotation->shared);
187 1
        $definition->setSynthetic($annotation->synthetic);
188 1
        $definition->setAbstract($annotation->abstract);
189 1
        $this->processConfigurator($annotation, $definition);
190
191 1
        if (isset($annotation->factory)) {
192
            $definition->setFactory($annotation->factory);
193
        }
194 1
    }
195
196
    /**
197
     * @param ReflectionClass $reflectionClass
198
     * @param Service $annotation
199
     * @param Definition $definition
200
     */
201 1
    private function processTags(
202
        ReflectionClass $reflectionClass,
203
        Service $annotation,
204
        Definition $definition
205
    ) {
206 1
        if (empty($annotation->tags)) {
207 1
            return;
208
        }
209
210 1
        foreach ($annotation->tags as $tag) {
211 1
            if (!isset($tag['name'])) {
212
                throw new InvalidArgumentException(
213
                    sprintf(
214
                        'A "tags" entry is missing a "name" key must be an array for class "%s" in %s.',
215
                        $reflectionClass->getName(),
216
                        $reflectionClass->getFileName()
217
                    )
218
                );
219
            }
220 1
            $name = $tag['name'];
221 1
            unset($tag['name']);
222
223 1
            $definition->addTag($name, $tag);
224
        }
225 1
    }
226
227
    /**
228
     * @param string $value
229
     * @return Reference
230
     */
231 1
    private function getValueReference($value) : Reference
232
    {
233 1
        if (0 === strpos($value, '@?')) {
234
            $value           = substr($value, 2);
235
            $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
236
        } else {
237 1
            $value           = substr($value, 1);
238 1
            $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
239
        }
240
241
        // mark reference as strict
242 1
        if ('=' === substr($value, -1)) {
243
            $value  = substr($value, 0, -1);
244
            $strict = false;
245
        } else {
246 1
            $strict = true;
247
        }
248
249 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...
250
    }
251
252
    /**
253
     * @param Service $annotation
254
     * @param Definition $definition
255
     */
256 1
    private function processConfigurator(Service $annotation, Definition $definition)
257
    {
258 1
        if (!isset($annotation->configurator)) {
259 1
            return;
260
        }
261
262
        if (is_string($annotation->configurator)) {
263
            $definition->setConfigurator($annotation->configurator);
264
        } else {
265
            $definition->setConfigurator([
266
                $this->resolveServices($annotation->configurator[0]),
267
                $annotation->configurator[1]
268
            ]);
269
        }
270
    }
271
272
    /**
273
     * @param Definition $definition
274
     * @param ReflectionMethod $method
275
     */
276 1
    protected function processMethod(Definition $definition, ReflectionMethod $method)
277
    {
278
        /** @var Inject $annotation */
279 1
        $annotation = $this->reader->getMethodAnnotation($method, Inject::class);
280 1
        if ($annotation) {
281 1
            $arguments = $this->extractArguments(
282
                $method,
283
                $annotation
284
            );
285 1
            $definition->addMethodCall($method->getName(), $arguments);
286
        }
287 1
    }
288
}
289