Completed
Push — master ( 465a06...3400f2 )
by Gerrit
01:50
created

ArgumentCompiler   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 213
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 98.84%

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 4
dl 0
loc 213
ccs 85
cts 86
cp 0.9884
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A buildArguments() 0 29 3
B buildCallArguments() 0 62 8
C resolveArgumentConfiguration() 0 92 12
1
<?php
2
/**
3
 * Copyright (C) 2018 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 *
8
 * @license GPL-3.0
9
 *
10
 * @author Gerrit Addiks <[email protected]>
11
 */
12
13
namespace Addiks\SymfonyGenerics\Services;
14
15
use Addiks\SymfonyGenerics\Services\ArgumentCompilerInterface;
16
use Psr\Container\ContainerInterface;
17
use ErrorException;
18
use ReflectionParameter;
19
use ReflectionType;
20
use ReflectionMethod;
21
use Symfony\Component\HttpFoundation\Request;
22
use Webmozart\Assert\Assert;
23
use Addiks\SymfonyGenerics\Services\EntityRepositoryInterface;
24
use ReflectionClass;
25
use ReflectionFunctionAbstract;
26
use InvalidArgumentException;
27
use ReflectionException;
28
29
final class ArgumentCompiler implements ArgumentCompilerInterface
30
{
31
32
    /**
33
     * @var ContainerInterface
34
     */
35
    private $container;
36
37
    /**
38
     * @var EntityRepositoryInterface
39
     */
40
    private $entityRepository;
41
42 6
    public function __construct(
43
        ContainerInterface $container,
44
        EntityRepositoryInterface $entityRepository
45
    ) {
46 6
        $this->container = $container;
47 6
        $this->entityRepository = $entityRepository;
48 6
    }
49
50 2
    public function buildArguments(
51
        array $argumentsConfiguration,
52
        Request $request
53
    ): array {
54
        /** @var array<int, mixed> $routeArguments */
55 2
        $routeArguments = array();
56
57 2
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
58
            /** @var array|string $argumentConfiguration */
59
60
            /** @var string|null $parameterTypeName */
61 2
            $parameterTypeName = null;
62
63 2
            if (isset($argumentConfiguration['entity-class'])) {
64
                $parameterTypeName = $argumentConfiguration['entity-class'];
65
            }
66
67
            /** @var mixed $argumentValue */
68 2
            $argumentValue = $this->resolveArgumentConfiguration(
69 2
                $argumentConfiguration,
70 2
                $request,
71 2
                $parameterTypeName
72
            );
73
74 2
            $routeArguments[$key] = $argumentValue;
75
        }
76
77 1
        return $routeArguments;
78
    }
79
80 5
    public function buildCallArguments(
81
        ReflectionFunctionAbstract $routineReflection,
82
        array $argumentsConfiguration,
83
        Request $request
84
    ): array {
85
        /** @var array<int, mixed> $callArguments */
86 5
        $callArguments = array();
87
88 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
89
            /** @var ReflectionParameter $parameterReflection */
90
91
            /** @var string $parameterName */
92 4
            $parameterName = $parameterReflection->getName();
0 ignored issues
show
Bug introduced by
Consider using $parameterReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
93
94
            /** @var mixed $requestValue */
95 4
            $requestValue = $request->get($parameterName);
96
97 4
            if (isset($argumentsConfiguration[$parameterName])) {
98
                /** @var array|string $argumentConfiguration */
99 4
                $argumentConfiguration = $argumentsConfiguration[$parameterName];
100
101 4
                Assert::true(is_string($argumentConfiguration) || is_array($argumentConfiguration));
102
103
                /** @var string|null $parameterTypeName */
104 3
                $parameterTypeName = null;
105
106 3
                if ($parameterReflection->hasType()) {
107
                    /** @var ReflectionType|null $parameterType */
108 1
                    $parameterType = $parameterReflection->getType();
109
110 1
                    if ($parameterType instanceof ReflectionType) {
0 ignored issues
show
Bug introduced by
The class ReflectionType does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
111 1
                        $parameterTypeName = $parameterType->__toString();
112
                    }
113
                }
114
115
                /** @var mixed $argumentValue */
116 3
                $argumentValue = $this->resolveArgumentConfiguration(
117 3
                    $argumentConfiguration,
118 3
                    $request,
119 3
                    $parameterTypeName
120
                );
121
122 1
                $callArguments[$index] = $argumentValue;
123
124 2
            } elseif (!is_null($requestValue)) {
125 1
                $callArguments[$index] = $requestValue;
126
127
            } else {
128
                try {
129 1
                    $callArguments[$index] = $parameterReflection->getDefaultValue();
130
131 1
                } catch (ReflectionException $exception) {
132 1
                    throw new InvalidArgumentException(sprintf(
133 1
                        "Missing argument '%s' for this call!",
134 2
                        $parameterName
135
                    ));
136
                }
137
            }
138
        }
139
140 2
        return $callArguments;
141
    }
142
143
    /**
144
     * @param array|string $argumentConfiguration
145
     *
146
     * @return mixed
147
     */
148 5
    private function resolveArgumentConfiguration(
149
        $argumentConfiguration,
150
        Request $request,
151
        ?string $parameterTypeName
152
    ) {
153
        /** @var mixed $argumentValue */
154 5
        $argumentValue = null;
155
156 5
        if (is_array($argumentConfiguration)) {
157 3
            if (isset($argumentConfiguration['id'])) {
158 3
                $argumentValue = $this->container->get($argumentConfiguration['id']);
159 3
                Assert::object($argumentValue, sprintf(
160 3
                    "Did not find service '%s'!",
161 3
                    $argumentConfiguration['id']
162
                ));
163
            }
164
165 2
            if (isset($argumentConfiguration['method'])) {
166 2
                $methodReflection = new ReflectionMethod($argumentValue, $argumentConfiguration['method']);
167
168 2
                if (!isset($argumentConfiguration['arguments'])) {
169 1
                    $argumentConfiguration['arguments'] = [];
170
                }
171
172
                /** @var array $callArguments */
173 2
                $callArguments = $this->buildCallArguments(
174 2
                    $methodReflection,
175 2
                    $argumentConfiguration['arguments'],
176 2
                    $request
177
                );
178
179 1
                $argumentValue = $methodReflection->invokeArgs($argumentValue, $callArguments);
180
            }
181
182
        } else {
183 3
            if (is_int(strpos($argumentConfiguration, '::'))) {
184 2
                [$factoryClass, $factoryMethod] = explode('::', $argumentConfiguration);
0 ignored issues
show
Bug introduced by
The variable $factoryClass does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $factoryMethod does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
185
186 2
                if (!empty($factoryClass)) {
187 2
                    if ($factoryClass[0] == '@') {
188
                        /** @var string $factoryServiceId */
189 2
                        $factoryServiceId = substr($factoryClass, 1);
190
191
                        /** @var object|null $factoryObject */
192 2
                        $factoryObject = $this->container->get($factoryServiceId);
193
194 2
                        Assert::methodExists($factoryObject, $factoryMethod, sprintf(
195 2
                            "Did not find service with id '%s' that has a method '%s'!",
196 2
                            $factoryServiceId,
197 2
                            $factoryMethod
198
                        ));
199
200 1
                        $factoryReflection = new ReflectionClass($factoryObject);
201
202
                        /** @var ReflectionMethod $methodReflection */
203 1
                        $methodReflection = $factoryReflection->getMethod($factoryMethod);
204
205 1
                        $callArguments = $this->buildCallArguments(
206 1
                            $methodReflection,
207 1
                            [], # TODO
208 1
                            $request
209
                        );
210
211
                        # Create by factory-service-object
212 1
                        $argumentValue = call_user_func_array([$factoryObject, $factoryMethod], $callArguments);
213
214
                    } else {
215
                        # Create by static factory-method of other class
216 1
                        $argumentValue = call_user_func_array($argumentConfiguration, []);
217
                    }
218
219 1
                } else {
220
                    # TODO: What to do here? What could "::Something" be? A template?
221
                }
222
223 3
            } elseif ($argumentConfiguration[0] == '$') {
224 3
                $argumentValue = $request->get(substr($argumentConfiguration, 1));
225
226 1
            } elseif ($argumentConfiguration[0] == '@') {
227 1
                $argumentValue = $this->container->get(substr($argumentConfiguration, 1));
228
            }
229
        }
230
231 3
        if (!empty($parameterTypeName)) {
232 1
            if (class_exists($parameterTypeName)) {
233 1
                $argumentValue = $this->entityRepository->findEntity($parameterTypeName, $argumentValue);
234
                # TODO: error handling "not an entty", "entity not found", ...
235
            }
236
        }
237
238 3
        return $argumentValue;
239
    }
240
241
}
242