CrossContainerProcessor::resolveArgument()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the CrossContainerExtension package.
7
 *
8
 * (c) Kamil Kokot <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace FriendsOfBehat\CrossContainerExtension;
15
16
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use Symfony\Component\DependencyInjection\Definition;
19
use Symfony\Component\DependencyInjection\Reference;
20
21
final class CrossContainerProcessor implements CompilerPassInterface
22
{
23
    /**
24
     * @var ContainerAccessor[]
25
     */
26
    private $containerAccessors;
27
28
    /**
29
     * @param ContainerAccessor[] $containerAccessors
30
     */
31
    public function __construct(array $containerAccessors = [])
32
    {
33
        foreach ($containerAccessors as $containerIdentifier => $containerAccessor) {
34
            $this->addContainerAccessor($containerIdentifier, $containerAccessor);
35
        }
36
    }
37
38
    /**
39
     * @param string $containerIdentifier
40
     * @param ContainerAccessor $containerAccessor
41
     */
42
    public function addContainerAccessor(string $containerIdentifier, ContainerAccessor $containerAccessor): void
43
    {
44
        $this->containerAccessors[$containerIdentifier] = $containerAccessor;
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function process(ContainerBuilder $container): void
51
    {
52
        $container->addDefinitions(array_map(function (Definition $definition) {
53
            return $this->resolveDefinition($definition);
54
        }, $container->getDefinitions()));
55
56
        $this->copyParameters($container);
57
    }
58
59
    /**
60
     * @param Definition $definition
61
     *
62
     * @return Definition
63
     */
64
    private function resolveDefinition(Definition $definition): Definition
65
    {
66
        $definition->setArguments($this->resolveArguments($definition->getArguments()));
67
        $definition->setFactory($this->resolveFactory($definition->getFactory()));
0 ignored issues
show
Bug introduced by
It seems like $definition->getFactory() targeting Symfony\Component\Depend...efinition::getFactory() can also be of type string; however, FriendsOfBehat\CrossCont...essor::resolveFactory() does only seem to accept array|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $this->resolveFactory($definition->getFactory()) targeting FriendsOfBehat\CrossCont...essor::resolveFactory() can also be of type null; however, Symfony\Component\Depend...efinition::setFactory() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
68
        $definition->setMethodCalls($this->resolveMethodCalls($definition->getMethodCalls()));
69
70
        return $definition;
71
    }
72
73
    /**
74
     * @param array|null $factory
75
     *
76
     * @return array|null
77
     */
78
    private function resolveFactory(?array $factory): ?array
79
    {
80
        if ([] === $factory) {
81
            return [];
82
        }
83
84
        if (isset($factory[0]) && $factory[0] instanceof Reference) {
85
            $factory[0] = $this->resolveReference($factory[0]);
86
        }
87
88
        return $factory;
89
    }
90
91
    /**
92
     * @param array $methodCalls
93
     *
94
     * @return array
95
     */
96
    private function resolveMethodCalls(array $methodCalls): array
97
    {
98
        return array_map(function (array $methodCall) {
99
            return [$methodCall[0], $this->resolveArguments($methodCall[1])];
100
        }, $methodCalls);
101
    }
102
103
    /**
104
     * @param array $arguments
105
     *
106
     * @return array
107
     */
108
    private function resolveArguments(array $arguments): array
109
    {
110
        return array_map(function ($argument) {
111
            return $this->resolveArgument($argument);
112
        }, $arguments);
113
    }
114
115
    /**
116
     * @param mixed $argument
117
     *
118
     * @return mixed
119
     */
120
    private function resolveArgument($argument)
121
    {
122
        if ($argument instanceof Definition) {
123
            return $this->resolveDefinition($argument);
124
        }
125
126
        if ($argument instanceof Reference) {
127
            return $this->resolveReference($argument);
128
        }
129
130
        if (is_array($argument)) {
131
            return $this->resolveArguments($argument);
132
        }
133
134
        return $argument;
135
    }
136
137
    /**
138
     * @param Reference $reference
139
     *
140
     * @return Definition|Reference
141
     */
142
    private function resolveReference(Reference $reference)
143
    {
144
        if (!ExternalReference::isValid($reference)) {
145
            return $reference;
146
        }
147
148
        return $this->transformReferenceToDefinition(new ExternalReference($reference));
149
    }
150
151
    /**
152
     * @param ExternalReference $externalReference
153
     *
154
     * @return Definition
155
     */
156
    private function transformReferenceToDefinition(ExternalReference $externalReference): Definition
157
    {
158
        $this->assertExternalReferenceHasKnownContainer($externalReference);
159
160
        $definition = new Definition(null, [$externalReference->serviceIdentifier()]);
161
        $definition->setFactory([$this->containerAccessors[$externalReference->containerIdentifier()], 'getService']);
162
163
        return $definition;
164
    }
165
166
    /**
167
     * @param ExternalReference $externalReference
168
     *
169
     * @throws \DomainException
170
     */
171
    private function assertExternalReferenceHasKnownContainer(ExternalReference $externalReference): void
172
    {
173
        if (!isset($this->containerAccessors[$externalReference->containerIdentifier()])) {
174
            throw new \DomainException(sprintf(
175
                'External container with identifier "%s" does not exist.',
176
                $externalReference->containerIdentifier()
177
            ));
178
        }
179
    }
180
181
    /**
182
     * @param ContainerBuilder $container
183
     */
184
    private function copyParameters(ContainerBuilder $container): void
185
    {
186
        foreach ($this->containerAccessors as $containerIdentifier => $containerAccessor) {
187
            foreach ($containerAccessor->getParameters() as $name => $value) {
188
                $container->setParameter(sprintf('__%s__.%s', $containerIdentifier, $name), $value);
189
            }
190
        }
191
    }
192
}
193