Completed
Push — master ( 6a8cc9...937b80 )
by Nikola
03:42
created

CompilerPass::process()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 25
cts 25
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 23
nc 9
nop 1
crap 6
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 9
    public function process(ContainerBuilder $container)
45
    {
46 9
        if (!$container->hasParameter('runopencode.traitor.injectables')) {
47 1
            return;
48
        }
49
50 8
        $getParameterOrDefault = function ($name, $default = []) use ($container) {
51
52 8
            if ($container->hasParameter($name)) {
53 6
                return $container->getParameter($name);
54
            }
55
56 8
            return $default;
57 8
        };
58
59 8
        $this->injectables = $container->getParameter('runopencode.traitor.injectables');
60 8
        $this->filter = [
61 8
            'tags' => array_combine($tags = $getParameterOrDefault('runopencode.traitor.filter.tags'), $tags),
62 8
            'namespaces' => $getParameterOrDefault('runopencode.traitor.filter.namespaces'),
63
        ];
64 8
        $this->exclude = [
65 8
            'tags' => array_combine($tags = $getParameterOrDefault('runopencode.traitor.exclude.tags'), $tags),
66 8
            'namespaces' => $getParameterOrDefault('runopencode.traitor.exclude.namespaces'),
67 8
            'classes' => array_combine($classes = $getParameterOrDefault('runopencode.traitor.exclude.classes'), $classes),
68 8
            'services' => array_combine($services = $getParameterOrDefault('runopencode.traitor.exclude.services'), $services),
69
        ];
70
71 8
        if (0 === count($this->filter['tags']) + count($this->filter['namespaces'])) {
72 6
            $this->filter = [];
73
        }
74
75 8
        if (0 === count($this->exclude['tags']) + count($this->exclude['namespaces']) + count($this->exclude['classes']) + count($this->exclude['services'])) {
76 4
            $this->exclude = [];
77
        }
78
79 8
        $injectableServices = $this->findInjectableServices($container);
80
81 8
        foreach ($injectableServices as $definition) {
82 8
            $this->processInjections($definition);
83
        }
84 8
    }
85
86
    /**
87
     * Find all services which should be injected with services via traits.
88
     *
89
     * @param ContainerBuilder $container
90
     * @return array
91
     */
92 8
    private function findInjectableServices(ContainerBuilder $container)
93
    {
94 8
        $services = [];
95
96 8
        foreach ($container->getDefinitions() as $serviceId => $definition) {
97
98 8
            if ($definition->isSynthetic() || !$definition->getClass()) {
99 1
                continue;
100
            }
101
102 8
            if ($this->isInjectable($serviceId, $definition) && !$this->isExcluded($serviceId, $definition)) {
103 8
                $services[$serviceId] = $definition;
104
            }
105
        }
106
107 8
        return $services;
108
    }
109
110
    /**
111
     * Check if service definition should be injected with service via traits.
112
     *
113
     * @param string $serviceId
114
     * @param Definition $definition
115
     * @return bool
116
     */
117 8
    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...
118
    {
119 8
        if (0 === count($this->filter)) {
120 6
            return true;
121
        }
122
123 2
        $class = $definition->getClass();
124
125 2
        foreach ($this->filter['namespaces'] as $namespace) {
126
127 1
            if (ClassUtils::isWithinNamespace($class, $namespace)) {
128 1
                return true;
129
            }
130
        }
131
132 2
        foreach ($definition->getTags() as $tag => $attributes) {
133
134 1
            if (isset($this->filter['tags'][$tag])) {
135 1
                return true;
136
            }
137
        }
138
139 2
        return false;
140
    }
141
142
    /**
143
     * Check if service definition should be excluded from service injection via traits.
144
     *
145
     * @param string $serviceId
146
     * @param Definition $definition
147
     * @return bool
148
     */
149 8
    private function isExcluded($serviceId, Definition $definition)
150
    {
151 8
        if (0 === count($this->exclude)) {
152 4
            return false;
153
        }
154
155 4
        if (isset($this->exclude['services'][$serviceId])) {
156 1
            return true;
157
        }
158
159 4
        $class = $definition->getClass();
160
161 4
        if (isset($this->exclude['classes'][$class])) {
162 1
            return true;
163
        }
164
165 4
        foreach ($this->exclude['namespaces'] as $namespace) {
166
167 1
            if (ClassUtils::isWithinNamespace($class, $namespace)) {
168 1
                return true;
169
            }
170
        }
171
172 4
        foreach ($definition->getTags() as $tag => $attribures) {
173
174 1
            if (isset($this->exclude['tags'][$tag])) {
175 1
                return true;
176
            }
177
        }
178
179 4
        return false;
180
    }
181
182
    /**
183
     * Process service injections via traits.
184
     *
185
     * @param Definition $definition
186
     */
187 8
    private function processInjections(Definition $definition)
188
    {
189 8
        $class = $definition->getClass();
190
191 8
        foreach ($this->injectables as $trait => $calls) {
192
193 8
            if (class_exists($class) && ClassUtils::usesTrait($class, $trait)) {
194 7
                foreach ($calls as $call) {
195 8
                    $definition->addMethodCall($call['method'], $this->processArguments($call['arguments']));
196
                }
197
            }
198
        }
199 8
    }
200
201
    /**
202
     * Process service injection parameters.
203
     *
204
     * @param array $arguments
205
     * @return array
206
     */
207 7
    private function processArguments(array $arguments)
208
    {
209 7
        $processed = [];
210
211 7
        foreach ($arguments as $argument) {
212 7
            $processed[] = $this->{sprintf('process%sAsArgument', ucfirst($argument['type']))}($argument);
213
        }
214
215 7
        return $processed;
216
    }
217
218
    /**
219
     * Process service as argument.
220
     *
221
     * @param array $argument
222
     * @return Reference
223
     */
224 7
    private function processServiceAsArgument(array $argument)
225
    {
226 7
        $invalidBehaviour = ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE;
227
228 7
        if (null === $argument['on_invalid']) {
229 7
            $invalidBehaviour = ContainerBuilder::NULL_ON_INVALID_REFERENCE;
230
        }
231
232 7
        if ('ignore' === $argument['on_invalid']) {
233 7
            $invalidBehaviour = ContainerBuilder::IGNORE_ON_INVALID_REFERENCE;
234
        }
235
236 7
        return new Reference($argument['id'], $invalidBehaviour);
237
    }
238
239
    /**
240
     * Process expression as argument.
241
     *
242
     * @param array $argument
243
     * @return Expression
244
     */
245 5
    private function processExpressionAsArgument(array $argument)
246
    {
247 5
        return new Expression($argument['value']);
248
    }
249
250
    /**
251
     * Process string as argument
252
     *
253
     * @param array $argument
254
     * @return string
255
     */
256 5
    private function processStringAsArgument(array $argument)
257
    {
258 5
        return (string) $argument['value'];
259
    }
260
261
    /**
262
     * Process constant as argument.
263
     *
264
     * @param array $argument
265
     * @return mixed
266
     */
267 5
    private function processConstantAsArgument(array $argument)
268
    {
269 5
        return constant($argument['value']);
270
    }
271
}
272