Completed
Push — master ( 3ed74c...61a0b2 )
by Nikola
07:24
created

CompilerPass::processConstantAsArgument()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/*
3
 * This file is part of the  TraitorBundle, an RunOpenCode project.
4
 *
5
 * (c) 2017 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\Bundle\Traitor\DependencyInjection;
11
12
use RunOpenCode\Bundle\Traitor\Utils\ClassUtils;
13
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
14
use Symfony\Component\DependencyInjection\ContainerBuilder;
15
use Symfony\Component\DependencyInjection\Definition;
16
use Symfony\Component\DependencyInjection\Reference;
17
use Symfony\Component\ExpressionLanguage\Expression;
18
19
/**
20
 * Class CompilerPass
21
 *
22
 * @package RunOpenCode\Bundle\Traitor\DependencyInjection
23
 */
24
class CompilerPass implements CompilerPassInterface
25
{
26
    /**
27
     * @var array
28
     */
29
    private $injectables;
30
31
    /**
32
     * @var array
33
     */
34
    private $filter;
35
36
    /**
37
     * @var array
38
     */
39
    private $exclude;
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 2
    public function process(ContainerBuilder $container)
45
    {
46 2
        if (!$container->hasParameter('runopencode.traitor.injectables')) {
47
            return;
48
        }
49
50 2
        $this->injectables = $container->getParameter('runopencode.traitor.injectables');
1 ignored issue
show
Documentation Bug introduced by
It seems like $container->getParameter...e.traitor.injectables') of type * is incompatible with the declared type array of property $injectables.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
51 2
        $this->filter = [
52 2
            'tags' => ($container->hasParameter('runopencode.traitor.filter.tags')) ? array_combine($tags = $container->getParameter('runopencode.traitor.filter.tags'), $tags) : [],
53 2
            'namespaces' => ($container->hasParameter('runopencode.traitor.filter.namespaces')) ? $container->getParameter('runopencode.traitor.filter.namespaces') : [],
54
        ];
55 2
        $this->exclude = [
56 2
            'tags' => ($container->hasParameter('runopencode.traitor.exclude.tags')) ? array_combine($tags = $container->getParameter('runopencode.traitor.exclude.tags'), $tags) : [],
57 2
            'namespaces' => ($container->hasParameter('runopencode.traitor.exclude.namespaces')) ? $container->getParameter('runopencode.traitor.exclude.namespaces') : [],
58 2
            'classes' => ($container->hasParameter('runopencode.traitor.exclude.classes')) ? array_combine($classes = $container->getParameter('runopencode.traitor.exclude.classes'), $classes) : [],
59 2
            'services' => ($container->hasParameter('runopencode.traitor.exclude.services')) ? array_combine($services = $container->getParameter('runopencode.traitor.exclude.services'), $services) : [],
60
        ];
61
62 2
        if (0 === count($this->filter['tags']) + count($this->filter['namespaces'])) {
63 2
            $this->filter = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $filter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
64
        }
65
66 2
        if (0 === count($this->exclude['tags']) + count($this->exclude['namespaces']) + count($this->exclude['classes']) + count($this->exclude['services'])) {
67 2
            $this->exclude = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $exclude.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
68
        }
69
70 2
        $injectableServices = $this->findInjectableServices($container);
71
72 2
        foreach ($injectableServices as $definition) {
73 2
            $this->processInjections($definition);
74
        }
75 2
    }
76
77
    /**
78
     * Find all services which should be injected with services via traits.
79
     *
80
     * @param ContainerBuilder $container
81
     * @return array
82
     */
83 2
    private function findInjectableServices(ContainerBuilder $container)
84
    {
85 2
        $services = [];
86
87 2
        foreach ($container->getDefinitions() as $serviceId => $definition) {
88
89 2
            if ($definition->isSynthetic() || !$definition->getClass()) {
90
                continue;
91
            }
92
93 2
            if ($this->isInjectable($serviceId, $definition) && !$this->isExcluded($serviceId, $definition)) {
94 2
                $services[$serviceId] = $definition;
95
            }
96
        }
97
98 2
        return $services;
99
    }
100
101
    /**
102
     * Check if service definition should be injected with service via traits.
103
     *
104
     * @param string $serviceId
105
     * @param Definition $definition
106
     * @return bool
107
     */
108 2
    private function isInjectable($serviceId, Definition $definition)
0 ignored issues
show
Unused Code introduced by
The parameter $serviceId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
109
    {
110 2
        if (null === $this->filter) {
111 2
            return true;
112
        }
113
114
        $class = $definition->getClass();
115
116
        foreach ($this->filter['namespaces'] as $namespace) {
117
118
            if (ClassUtils::isWithinNamespace($class, $namespace)) {
119
                return true;
120
            }
121
        }
122
123
        foreach ($definition->getTags() as $tag) {
124
125
            if (isset($tag['name'], $this->filter['tags'][$tag['name']])) {
126
                return true;
127
            }
128
        }
129
130
        return false;
131
    }
132
133
    /**
134
     * Check if service definition should be excluded from service injection via traits.
135
     *
136
     * @param string $serviceId
137
     * @param Definition $definition
138
     * @return bool
139
     */
140 2
    private function isExcluded($serviceId, Definition $definition)
141
    {
142 2
        if (null === $this->exclude) {
143 2
            return false;
144
        }
145
146
        if (isset($this->exclude['services'][$serviceId])) {
147
            return true;
148
        }
149
150
        $class = $definition->getClass();
151
152
        if (isset($this->exclude['classes'][$class])) {
153
            return true;
154
        }
155
156
        foreach ($this->exclude['namespaces'] as $namespace) {
157
158
            if (ClassUtils::isWithinNamespace($class, $namespace)) {
159
                return true;
160
            }
161
        }
162
163
        foreach ($definition->getTags() as $tag) {
164
165
            if (isset($tag['name'], $this->exclude['tags'][$tag['name']])) {
166
                return true;
167
            }
168
        }
169
170
        return false;
171
    }
172
173
    /**
174
     * Process service injections via traits.
175
     *
176
     * @param Definition $definition
177
     */
178 2
    private function processInjections(Definition $definition)
179
    {
180 2
        $class = $definition->getClass();
181
182 2
        foreach ($this->injectables as $trait => $calls) {
183
184 2
            if (class_exists($class) && ClassUtils::usesTrait($class, $trait)) {
185 2
                foreach ($calls as $call) {
186 2
                    $definition->addMethodCall($call['method'], $this->processArguments($call['arguments']));
187
                }
188
            }
189
        }
190 2
    }
191
192
    /**
193
     * Process service injection parameters.
194
     *
195
     * @param array $arguments
196
     * @return array
197
     */
198 2
    private function processArguments(array $arguments)
199
    {
200 2
        $processed = [];
201
202 2
        foreach ($arguments as $argument) {
203 2
            $processed[] = $this->{sprintf('process%sAsArgument', ucfirst($argument['type']))}($argument);
204
        }
205
206 2
        return $processed;
207
    }
208
209
    /**
210
     * Process service as argument.
211
     *
212
     * @param array $argument
213
     * @return Reference
214
     */
215 2
    private function processServiceAsArgument(array $argument)
216
    {
217 2
        $invalidBehaviour = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE;
218
219 2
        if (null === $argument['on_invalid']) {
220 2
            $invalidBehaviour = ContainerBuilder::NULL_ON_INVALID_REFERENCE;
221
        }
222
223 2
        if ('ignore' === $argument['on_invalid']) {
224 2
            $invalidBehaviour = ContainerBuilder::IGNORE_ON_INVALID_REFERENCE;
225
        }
226
227 2
        return new Reference($argument['id'], $invalidBehaviour);
228
    }
229
230
    /**
231
     * Process expression as argument.
232
     *
233
     * @param array $argument
234
     * @return Expression
235
     */
236 2
    private function processExpressionAsArgument(array $argument)
237
    {
238 2
        return new Expression($argument['value']);
239
    }
240
241
    /**
242
     * Process string as argument
243
     *
244
     * @param array $argument
245
     * @return string
246
     */
247 2
    private function processStringAsArgument(array $argument)
248
    {
249 2
        return (string) $argument['value'];
250
    }
251
252
    /**
253
     * Process constant as argument.
254
     *
255
     * @param array $argument
256
     * @return mixed
257
     */
258 2
    private function processConstantAsArgument(array $argument)
259
    {
260 2
        return constant($argument['value']);
261
    }
262
}
263