Passed
Push — master ( a755ea...261f35 )
by Gerrit
04:21
created

ArgumentCompiler::buildCallArguments()   C

Complexity

Conditions 11
Paths 22

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11.2363

Importance

Changes 0
Metric Value
dl 0
loc 74
ccs 28
cts 32
cp 0.875
rs 6.4206
c 0
b 0
f 0
cc 11
nc 22
nop 3
crap 11.2363

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 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
                    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...
128
                        $argumentValue = $parameterTypeName::fromNative($requestValue);
129
130
                        $callArguments[$index] = $argumentValue;
131
                    }
132
133
                } else {
134 1
                    $callArguments[$index] = $requestValue;
135
                }
136
137 1
            } elseif ($parameterTypeName === Request::class) {
138
                $callArguments[$index] = $request;
139
140
            } else {
141
                try {
142 1
                    $callArguments[$index] = $parameterReflection->getDefaultValue();
143
144 1
                } catch (ReflectionException $exception) {
145 1
                    throw new InvalidArgumentException(sprintf(
146 1
                        "Missing argument '%s' for this call!",
147 2
                        $parameterName
148
                    ));
149
                }
150
            }
151
        }
152
153 2
        return $callArguments;
154
    }
155
156
    /**
157
     * @param array|string $argumentConfiguration
158
     *
159
     * @return mixed
160
     */
161 5
    private function resolveArgumentConfiguration(
162
        $argumentConfiguration,
163
        Request $request,
164
        ?string $parameterTypeName
165
    ) {
166
        /** @var mixed $argumentValue */
167 5
        $argumentValue = null;
168
169 5
        if (is_array($argumentConfiguration)) {
170 3
            if (isset($argumentConfiguration['id'])) {
171 3
                $argumentValue = $this->container->get($argumentConfiguration['id']);
172 3
                Assert::object($argumentValue, sprintf(
173 3
                    "Did not find service '%s'!",
174 3
                    $argumentConfiguration['id']
175
                ));
176
            }
177
178 2
            if (isset($argumentConfiguration['method'])) {
179 2
                $methodReflection = new ReflectionMethod($argumentValue, $argumentConfiguration['method']);
180
181 2
                if (!isset($argumentConfiguration['arguments'])) {
182 1
                    $argumentConfiguration['arguments'] = [];
183
                }
184
185
                /** @var array $callArguments */
186 2
                $callArguments = $this->buildCallArguments(
187 2
                    $methodReflection,
188 2
                    $argumentConfiguration['arguments'],
189 2
                    $request
190
                );
191
192 1
                $argumentValue = $methodReflection->invokeArgs($argumentValue, $callArguments);
193
            }
194
195
        } else {
196 3
            if (is_int(strpos($argumentConfiguration, '::'))) {
197 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...
198
199 2
                if (!empty($factoryClass)) {
200 2
                    if ($factoryClass[0] == '@') {
201
                        /** @var string $factoryServiceId */
202 2
                        $factoryServiceId = substr($factoryClass, 1);
203
204
                        /** @var object|null $factoryObject */
205 2
                        $factoryObject = $this->container->get($factoryServiceId);
206
207 2
                        Assert::methodExists($factoryObject, $factoryMethod, sprintf(
208 2
                            "Did not find service with id '%s' that has a method '%s'!",
209 2
                            $factoryServiceId,
210 2
                            $factoryMethod
211
                        ));
212
213 1
                        $factoryReflection = new ReflectionClass($factoryObject);
214
215
                        /** @var ReflectionMethod $methodReflection */
216 1
                        $methodReflection = $factoryReflection->getMethod($factoryMethod);
217
218 1
                        $callArguments = $this->buildCallArguments(
219 1
                            $methodReflection,
220 1
                            [], # TODO
221 1
                            $request
222
                        );
223
224
                        # Create by factory-service-object
225 1
                        $argumentValue = call_user_func_array([$factoryObject, $factoryMethod], $callArguments);
226
227
                    } else {
228
                        # Create by static factory-method of other class
229 1
                        $argumentValue = call_user_func_array($argumentConfiguration, []);
230
                    }
231
232 1
                } else {
233
                    # TODO: What to do here? What could "::Something" be? A template?
234
                }
235
236 3
            } elseif ($argumentConfiguration[0] == '$') {
237 3
                $argumentValue = $request->get(substr($argumentConfiguration, 1));
238
239 1
            } elseif ($argumentConfiguration[0] == '@') {
240 1
                $argumentValue = $this->container->get(substr($argumentConfiguration, 1));
241
            }
242
        }
243
244 3
        if (!empty($parameterTypeName)) {
245 1
            if (class_exists($parameterTypeName)) {
246 1
                $argumentValue = $this->entityManager->find($parameterTypeName, $argumentValue);
247
                # TODO: error handling "not an entty", "entity not found", ...
248
            }
249
        }
250
251 3
        return $argumentValue;
252
    }
253
254
}
255