Passed
Push — master ( 261f35...f81515 )
by Gerrit
09:50
created

resolveStringArgumentConfiguration()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 56
ccs 26
cts 26
cp 1
rs 8.3377
c 0
b 0
f 0
cc 6
nc 6
nop 2
crap 6

How to fix   Long Method   

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 6
    public function __construct(
44
        ContainerInterface $container,
45
        EntityManagerInterface $entityManager
46
    ) {
47 6
        $this->container = $container;
48 6
        $this->entityManager = $entityManager;
49 6
    }
50
51 2
    public function buildArguments(
52
        array $argumentsConfiguration,
53
        Request $request
54
    ): array {
55
        /** @var array<int, mixed> $routeArguments */
56 2
        $routeArguments = array();
57
58 2
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
59
            /** @var array|string $argumentConfiguration */
60
61
            /** @var string|null $parameterTypeName */
62 2
            $parameterTypeName = null;
63
64 2
            if (isset($argumentConfiguration['entity-class'])) {
65
                $parameterTypeName = $argumentConfiguration['entity-class'];
66
            }
67
68
            /** @var mixed $argumentValue */
69 2
            $argumentValue = $this->resolveArgumentConfiguration(
70 2
                $argumentConfiguration,
71 2
                $request,
72 2
                $parameterTypeName
73
            );
74
75 2
            $routeArguments[$key] = $argumentValue;
76
        }
77
78 1
        return $routeArguments;
79
    }
80
81 5
    public function buildCallArguments(
82
        ReflectionFunctionAbstract $routineReflection,
83
        array $argumentsConfiguration,
84
        Request $request
85
    ): array {
86
        /** @var array<int, mixed> $callArguments */
87 5
        $callArguments = array();
88
89 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
90
            /** @var ReflectionParameter $parameterReflection */
91
92
            /** @var string $parameterName */
93 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...
94
95
            /** @var mixed $requestValue */
96 4
            $requestValue = $request->get($parameterName);
97
98
            /** @var string|null $parameterTypeName */
99 4
            $parameterTypeName = null;
100
101 4
            if ($parameterReflection->hasType()) {
102
                /** @var ReflectionType|null $parameterType */
103 2
                $parameterType = $parameterReflection->getType();
104
105 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...
106 2
                    $parameterTypeName = $parameterType->__toString();
107
                }
108
            }
109
110 4
            if (isset($argumentsConfiguration[$parameterName])) {
111
                /** @var array|string $argumentConfiguration */
112 4
                $argumentConfiguration = $argumentsConfiguration[$parameterName];
113
114 4
                Assert::true(is_string($argumentConfiguration) || is_array($argumentConfiguration));
115
116
                /** @var mixed $argumentValue */
117 3
                $argumentValue = $this->resolveArgumentConfiguration(
118 3
                    $argumentConfiguration,
119 3
                    $request,
120 3
                    $parameterTypeName
121
                );
122
123 1
                $callArguments[$index] = $argumentValue;
124
125 2
            } elseif (!is_null($requestValue)) {
126 1
                if (!is_null($parameterTypeName)) {
127
                    /** @psalm-suppress UndefinedClass ValueObjects\ValueObjectInterface does not exist */
128
                    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...
129
                        $argumentValue = $parameterTypeName::fromNative($requestValue);
130
131
                        $callArguments[$index] = $argumentValue;
132
                    }
133
134
                } else {
135 1
                    $callArguments[$index] = $requestValue;
136
                }
137
138 1
            } elseif ($parameterTypeName === Request::class) {
139
                $callArguments[$index] = $request;
140
141
            } else {
142
                try {
143 1
                    $callArguments[$index] = $parameterReflection->getDefaultValue();
144
145 1
                } catch (ReflectionException $exception) {
146 1
                    throw new InvalidArgumentException(sprintf(
147 1
                        "Missing argument '%s' for this call!",
148 2
                        $parameterName
149
                    ));
150
                }
151
            }
152
        }
153
154 2
        return $callArguments;
155
    }
156
157
    /**
158
     * @param array|string $argumentConfiguration
159
     *
160
     * @return mixed
161
     */
162 5
    private function resolveArgumentConfiguration(
163
        $argumentConfiguration,
164
        Request $request,
165
        ?string $parameterTypeName
166
    ) {
167
        /** @var mixed $argumentValue */
168 5
        $argumentValue = null;
169
170 5
        if (is_array($argumentConfiguration)) {
171 3
            if (!empty($parameterTypeName) && isset($argumentConfiguration['entity-id'])) {
172
                /** @var string $entityId */
173
                $entityId = $argumentConfiguration['entity-id'];
174
                $entityId = $this->resolveStringArgumentConfiguration($entityId, $request);
175
176
                $argumentValue = $this->entityManager->find(
177
                    $parameterTypeName,
178
                    $entityId
179
                );
180
181 3
            } elseif (isset($argumentConfiguration['id'])) {
182 3
                $argumentValue = $this->container->get($argumentConfiguration['id']);
183 3
                Assert::object($argumentValue, sprintf(
184 3
                    "Did not find service '%s'!",
185 3
                    $argumentConfiguration['id']
186
                ));
187
            }
188
189 2
            if (isset($argumentConfiguration['method'])) {
190 2
                $methodReflection = new ReflectionMethod($argumentValue, $argumentConfiguration['method']);
191
192 2
                if (!isset($argumentConfiguration['arguments'])) {
193 1
                    $argumentConfiguration['arguments'] = [];
194
                }
195
196
                /** @var array $callArguments */
197 2
                $callArguments = $this->buildCallArguments(
198 2
                    $methodReflection,
199 2
                    $argumentConfiguration['arguments'],
200 2
                    $request
201
                );
202
203 1
                $argumentValue = $methodReflection->invokeArgs($argumentValue, $callArguments);
204
            }
205
206
        } else {
207 3
            $argumentValue = $this->resolveStringArgumentConfiguration($argumentConfiguration, $request);
208
        }
209
210 3
        if (!empty($parameterTypeName)) {
211 1
            if (class_exists($parameterTypeName)) {
212 1
                $argumentValue = $this->entityManager->find($parameterTypeName, $argumentValue);
213
                # TODO: error handling "not an entty", "entity not found", ...
214
            }
215
        }
216
217 3
        return $argumentValue;
218
    }
219
220
    /**
221
     * @return mixed
222
     */
223 3
    private function resolveStringArgumentConfiguration(
224
        string $argumentConfiguration,
225
        Request $request
226
    ) {
227
        /** @var mixed $argumentValue */
228 3
        $argumentValue = null;
229
230 3
        if (is_int(strpos($argumentConfiguration, '::'))) {
231 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...
232
233 2
            if (!empty($factoryClass)) {
234 2
                if ($factoryClass[0] == '@') {
235
                    /** @var string $factoryServiceId */
236 2
                    $factoryServiceId = substr($factoryClass, 1);
237
238
                    /** @var object|null $factoryObject */
239 2
                    $factoryObject = $this->container->get($factoryServiceId);
240
241 2
                    Assert::methodExists($factoryObject, $factoryMethod, sprintf(
242 2
                        "Did not find service with id '%s' that has a method '%s'!",
243 2
                        $factoryServiceId,
244 2
                        $factoryMethod
245
                    ));
246
247 1
                    $factoryReflection = new ReflectionClass($factoryObject);
248
249
                    /** @var ReflectionMethod $methodReflection */
250 1
                    $methodReflection = $factoryReflection->getMethod($factoryMethod);
251
252 1
                    $callArguments = $this->buildCallArguments(
253 1
                        $methodReflection,
254 1
                        [], # TODO
255 1
                        $request
256
                    );
257
258
                    # Create by factory-service-object
259 1
                    $argumentValue = call_user_func_array([$factoryObject, $factoryMethod], $callArguments);
260
261
                } else {
262
                    # Create by static factory-method of other class
263 1
                    $argumentValue = call_user_func_array($argumentConfiguration, []);
264
                }
265
266 1
            } else {
267
                # TODO: What to do here? What could "::Something" be? A template?
268
            }
269
270 3
        } elseif ($argumentConfiguration[0] == '$') {
271 3
            $argumentValue = $request->get(substr($argumentConfiguration, 1));
272
273 1
        } elseif ($argumentConfiguration[0] == '@') {
274 1
            $argumentValue = $this->container->get(substr($argumentConfiguration, 1));
275
        }
276
277 3
        return $argumentValue;
278
    }
279
280
}
281