Passed
Push — master ( d0187e...7d6d5a )
by Gerrit
62:29
created

resolveStringArgumentConfiguration()   C

Complexity

Conditions 10
Paths 9

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 14.2875

Importance

Changes 0
Metric Value
dl 0
loc 84
ccs 26
cts 40
cp 0.65
rs 6.4824
c 0
b 0
f 0
cc 10
nc 9
nop 3
crap 14.2875

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 ReflectionClass;
24
use ReflectionFunctionAbstract;
25
use InvalidArgumentException;
26
use ReflectionException;
27
use Doctrine\ORM\EntityManagerInterface;
28
use ValueObjects\ValueObjectInterface;
29
30
final class ArgumentCompiler implements ArgumentCompilerInterface
31
{
32
33
    /**
34
     * @var ContainerInterface
35
     */
36
    private $container;
37
38
    /**
39
     * @var EntityManagerInterface
40
     */
41
    private $entityManager;
42
43 5
    public function __construct(
44
        ContainerInterface $container,
45
        EntityManagerInterface $entityManager
46
    ) {
47 5
        $this->container = $container;
48 5
        $this->entityManager = $entityManager;
49 5
    }
50
51 2
    public function buildArguments(
52
        array $argumentsConfiguration,
53
        Request $request,
54
        array $additionalData = array()
55
    ): array {
56
        /** @var array<int, mixed> $routeArguments */
57 2
        $routeArguments = array();
58
59 2
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
60
            /** @var array|string $argumentConfiguration */
61
62
            /** @var string|null $parameterTypeName */
63 2
            $parameterTypeName = null;
64
65 2
            if (isset($argumentConfiguration['entity-class'])) {
66
                $parameterTypeName = $argumentConfiguration['entity-class'];
67
            }
68
69
            /** @var mixed $argumentValue */
70 2
            $argumentValue = $this->resolveArgumentConfiguration(
71 2
                $argumentConfiguration,
72 2
                $request,
73 2
                $parameterTypeName,
74 2
                $additionalData
75
            );
76
77 2
            $routeArguments[$key] = $argumentValue;
78
        }
79
80 1
        return $routeArguments;
81
    }
82
83 4
    public function buildCallArguments(
84
        ReflectionFunctionAbstract $routineReflection,
85
        array $argumentsConfiguration,
86
        Request $request,
87
        array $additionalData = array()
88
    ): array {
89
        /** @var array<int, mixed> $callArguments */
90 4
        $callArguments = array();
91
92 4
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
93
            /** @var ReflectionParameter $parameterReflection */
94
95
            /** @var string $parameterName */
96 3
            $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...
97
98
            /** @var mixed $requestValue */
99 3
            $requestValue = $request->get($parameterName);
100
101
            /** @var string|null $parameterTypeName */
102 3
            $parameterTypeName = null;
103
104 3
            if ($parameterReflection->hasType()) {
105
                /** @var ReflectionType|null $parameterType */
106 2
                $parameterType = $parameterReflection->getType();
107
108 2
                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...
109 2
                    $parameterTypeName = $parameterType->__toString();
110
                }
111
            }
112
113 3
            if (isset($argumentsConfiguration[$parameterName])) {
114
                /** @var array|string $argumentConfiguration */
115 3
                $argumentConfiguration = $argumentsConfiguration[$parameterName];
116
117
                /** @var mixed $argumentValue */
118 3
                $argumentValue = $argumentConfiguration;
119
120 3
                if (is_string($argumentConfiguration) || is_array($argumentConfiguration)) {
121
                    /** @var mixed $argumentValue */
122 3
                    $argumentValue = $this->resolveArgumentConfiguration(
123 3
                        $argumentConfiguration,
124 3
                        $request,
125 3
                        $parameterTypeName,
126 3
                        $additionalData
127
                    );
128
                }
129
130 1
                $callArguments[$index] = $argumentValue;
131
132 2
            } elseif (!is_null($requestValue)) {
133 1
                if (!is_null($parameterTypeName)) {
134
                    /** @psalm-suppress UndefinedClass ValueObjects\ValueObjectInterface does not exist */
135
                    if (is_subclass_of($parameterTypeName, ValueObjectInterface::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \ValueObjects\ValueObjectInterface::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
136
                        $argumentValue = $parameterTypeName::fromNative($requestValue);
137
138
                        $callArguments[$index] = $argumentValue;
139
                    }
140
141
                } else {
142 1
                    $callArguments[$index] = $requestValue;
143
                }
144
145 1
            } elseif ($parameterTypeName === Request::class) {
146
                $callArguments[$index] = $request;
147
148
            } else {
149
                try {
150 1
                    $callArguments[$index] = $parameterReflection->getDefaultValue();
151
152 1
                } catch (ReflectionException $exception) {
153 1
                    throw new InvalidArgumentException(sprintf(
154 1
                        "Missing argument '%s' for this call!",
155 2
                        $parameterName
156
                    ));
157
                }
158
            }
159
        }
160
161 2
        return $callArguments;
162
    }
163
164
    /**
165
     * @param array|string $argumentConfiguration
166
     *
167
     * @return mixed
168
     */
169 5
    private function resolveArgumentConfiguration(
170
        $argumentConfiguration,
171
        Request $request,
172
        ?string $parameterTypeName,
173
        array $additionalData = array()
174
    ) {
175
        /** @var mixed $argumentValue */
176 5
        $argumentValue = null;
177
178 5
        if (is_array($argumentConfiguration)) {
179 3
            if (!empty($parameterTypeName) && isset($argumentConfiguration['entity-id'])) {
180
                /** @var string $entityId */
181
                $entityId = $argumentConfiguration['entity-id'];
182
                $entityId = $this->resolveStringArgumentConfiguration(
183
                    $entityId,
184
                    $request,
185
                    $additionalData
186
                );
187
188
                $argumentValue = $this->entityManager->find(
189
                    $parameterTypeName,
190
                    $entityId
191
                );
192
193 3
            } elseif (isset($argumentConfiguration['id'])) {
194 3
                $argumentValue = $this->container->get($argumentConfiguration['id']);
195 3
                Assert::object($argumentValue, sprintf(
196 3
                    "Did not find service '%s'!",
197 3
                    $argumentConfiguration['id']
198
                ));
199
            }
200
201 2
            if (isset($argumentConfiguration['method'])) {
202 2
                $methodReflection = new ReflectionMethod($argumentValue, $argumentConfiguration['method']);
203
204 2
                if (!isset($argumentConfiguration['arguments'])) {
205 1
                    $argumentConfiguration['arguments'] = [];
206
                }
207
208
                /** @var array $callArguments */
209 2
                $callArguments = $this->buildCallArguments(
210 2
                    $methodReflection,
211 2
                    $argumentConfiguration['arguments'],
212 2
                    $request
213
                );
214
215 1
                $argumentValue = $methodReflection->invokeArgs($argumentValue, $callArguments);
216
            }
217
218
        } else {
219 3
            $argumentValue = $this->resolveStringArgumentConfiguration(
220 3
                $argumentConfiguration,
221 3
                $request,
222 3
                $additionalData
223
            );
224
        }
225
226 3
        if (!empty($parameterTypeName)) {
227 1
            if (class_exists($parameterTypeName)) {
228 1
                $argumentValue = $this->entityManager->find($parameterTypeName, $argumentValue);
229
                # TODO: error handling "not an entty", "entity not found", ...
230
            }
231
        }
232
233 3
        return $argumentValue;
234
    }
235
236
    /**
237
     * @return mixed
238
     */
239 3
    private function resolveStringArgumentConfiguration(
240
        string $argumentConfiguration,
241
        Request $request,
242
        array $additionalData = array()
243
    ) {
244
        /** @var mixed $argumentValue */
245 3
        $argumentValue = null;
246
247 3
        if (is_int(strpos($argumentConfiguration, '::'))) {
248 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...
249
250 2
            if (!empty($factoryClass)) {
251 2
                if ($factoryClass[0] == '@') {
252
                    /** @var string $factoryServiceId */
253 2
                    $factoryServiceId = substr($factoryClass, 1);
254
255
                    /** @var object|null $factoryObject */
256 2
                    $factoryObject = $this->container->get($factoryServiceId);
257
258 2
                    Assert::methodExists($factoryObject, $factoryMethod, sprintf(
259 2
                        "Did not find service with id '%s' that has a method '%s'!",
260 2
                        $factoryServiceId,
261 2
                        $factoryMethod
262
                    ));
263
264 1
                    $factoryReflection = new ReflectionClass($factoryObject);
265
266
                    /** @var ReflectionMethod $methodReflection */
267 1
                    $methodReflection = $factoryReflection->getMethod($factoryMethod);
268
269 1
                    $callArguments = $this->buildCallArguments(
270 1
                        $methodReflection,
271 1
                        [], # TODO
272 1
                        $request
273
                    );
274
275
                    # Create by factory-service-object
276 1
                    $argumentValue = call_user_func_array([$factoryObject, $factoryMethod], $callArguments);
277
278
                } else {
279
                    # Create by static factory-method of other class
280 1
                    $argumentValue = call_user_func_array($argumentConfiguration, []);
281
                }
282
283 1
            } else {
284
                # TODO: What to do here? What could "::Something" be? A template?
285
            }
286
287 3
        } elseif ($argumentConfiguration[0] == '$') {
288 3
            $argumentValue = $request->get(substr($argumentConfiguration, 1));
289
290 1
        } elseif ($argumentConfiguration[0] == '@') {
291 1
            $argumentValue = $this->container->get(substr($argumentConfiguration, 1));
292
293
        } elseif ($argumentConfiguration[0] == '%') {
294
            /** @var string $key */
295
            $key = substr($argumentConfiguration, 1);
296
297
            if (is_int(strpos($key, '.'))) {
298
                [$key, $property] = explode('.', $key);
0 ignored issues
show
Bug introduced by
The variable $property 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...
299
300
                Assert::keyExists($additionalData, $key, sprintf(
301
                    'Missing additional-data key "%s"',
302
                    $key
303
                ));
304
305
                $argumentValue = $additionalData[$key];
306
307
                if (is_object($argumentValue) && method_exists($argumentValue, $property)) {
308
                    $argumentValue = call_user_func([$argumentValue, $property]);
309
                }
310
311
            } else {
312
                Assert::keyExists($additionalData, $key, sprintf(
313
                    'Missing additional-data key "%s"',
314
                    $key
315
                ));
316
317
                $argumentValue = $additionalData[$key];
318
            }
319
        }
320
321 3
        return $argumentValue;
322
    }
323
324
}
325