Passed
Push — master ( 8ce13f...c2b4ea )
by Gerrit
07:12
created

getDefaultValueFromParameterReflectionSafely()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 23
ccs 10
cts 11
cp 0.9091
crap 3.0067
rs 9.552
c 0
b 0
f 0
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 Addiks\SymfonyGenerics\Arguments\ArgumentFactory\ArgumentFactory;
17
use Addiks\SymfonyGenerics\Arguments\Argument;
18
use Symfony\Component\HttpFoundation\Request;
19
use Webmozart\Assert\Assert;
20
use ErrorException;
21
use ReflectionType;
22
use ReflectionNamedType;
23
use ReflectionFunctionAbstract;
24
use ReflectionParameter;
25
use ReflectionException;
26
use Symfony\Component\HttpFoundation\RequestStack;
27
use Addiks\SymfonyGenerics\Arguments\ArgumentContextInterface;
28
use InvalidArgumentException;
29
use Addiks\SymfonyGenerics\Controllers\ControllerHelperInterface;
30
use ValueObjects\ValueObjectInterface;
31
use Exception;
32
33
final class ArgumentCompiler implements ArgumentCompilerInterface
34
{
35
36
    /**
37
     * @var ArgumentFactory
38
     */
39
    private $argumentFactory;
40
41
    /**
42
     * @var RequestStack
43
     */
44
    private $requestStack;
45
46
    /**
47
     * @var ArgumentContextInterface
48
     */
49
    private $argumentContext;
50
51
    /**
52
     * @var ControllerHelperInterface
53
     */
54
    private $controllerHelper;
55
56 11
    public function __construct(
57
        ArgumentFactory $argumentFactory,
58
        RequestStack $requestStack,
59
        ArgumentContextInterface $argumentContext,
60
        ControllerHelperInterface $controllerHelper
61
    ) {
62 11
        $this->argumentFactory = $argumentFactory;
63 11
        $this->requestStack = $requestStack;
64 11
        $this->argumentContext = $argumentContext;
65 11
        $this->controllerHelper = $controllerHelper;
66 11
    }
67
68
    public function understandsArgumentString(string $argumentConfiguration): bool
69
    {
70
        return $this->argumentFactory->understandsString($argumentConfiguration);
71
    }
72
73
    /** @param array|string $argumentConfiguration */
74
    public function buildArgument($argumentConfiguration, array $additionalData = array())
75
    {
76
        foreach ($additionalData as $key => $value) {
77
            $this->argumentContext->set($key, $value);
78
        }
79
80
        return $this->resolveArgumentConfiguration($argumentConfiguration);
81
    }
82
83 6
    public function buildArguments(
84
        array $argumentsConfiguration,
85
        array $additionalData = array()
86
    ): array {
87
        /** @var array $argumentValues */
88 6
        $argumentValues = array();
89
90 6
        foreach ($additionalData as $key => $value) {
91 3
            $this->argumentContext->set($key, $value);
92
        }
93
94 6
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
95
            /** @var array|string $argumentConfiguration */
96
97 5
            $argumentValues[$key] = $this->resolveArgumentConfiguration($argumentConfiguration);
98
        }
99
100 3
        return $argumentValues;
101
    }
102
103 5
    public function buildCallArguments(
104
        ReflectionFunctionAbstract $methodReflection,
105
        array $argumentsConfiguration,
106
        array $predefinedArguments = array(),
107
        array $additionalData = array()
108
    ): array {
109
        /** @var array<int, mixed> $callArguments */
110 5
        $callArguments = array();
111
112 5
        foreach ($additionalData as $key => $value) {
113 4
            $this->argumentContext->set($key, $value);
114
        }
115
116 5
        foreach ($methodReflection->getParameters() as $index => $parameterReflection) {
117
            /** @var ReflectionParameter $parameterReflection */
118
119 4
            if (isset($predefinedArguments[$index])) {
120 2
                $callArguments[$index] = $predefinedArguments[$index];
121 2
                continue;
122
            }
123
124 4
            $callArguments[$index] = $this->resolveParameterReflection(
125 4
                $parameterReflection,
126
                $argumentsConfiguration,
127
                $index
128
            );
129
        }
130
131 4
        return $callArguments;
132
    }
133
134
    /**
135
     * @param array|string|bool|object|null $argumentConfiguration
136
     *
137
     * @return mixed
138
     */
139 8
    private function resolveArgumentConfiguration($argumentConfiguration)
140
    {
141 8
        Assert::oneOf(
142 8
            gettype($argumentConfiguration),
143 8
            ['string', 'array', 'NULL', 'boolean', 'object'],
144 8
            "Arguments must be defined as string, array, bool, object or null!"
145
        );
146
147
        /** @var Argument|null $argument */
148 7
        $argument = null;
0 ignored issues
show
Unused Code introduced by
$argument is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
149
150 7
        if (is_bool($argumentConfiguration) || is_null($argumentConfiguration) || is_object($argumentConfiguration)) {
151
            return $argumentConfiguration;
152
153 7
        } else if ($argumentConfiguration === '') {
154
            return '';
155
156 7
        } elseif (is_array($argumentConfiguration)) {
157 2
            Assert::true($this->argumentFactory->understandsArray($argumentConfiguration), sprintf(
158 2
                "Argument '%s' could not be understood!",
159 2
                preg_replace("/\s+/is", "", var_export($argumentConfiguration, true))
160
            ));
161
162 1
            $argument = $this->argumentFactory->createArgumentFromArray($argumentConfiguration);
163
164
        } else {
165 5
            Assert::true($this->argumentFactory->understandsString($argumentConfiguration), sprintf(
166 5
                "Argument '%s' could not be understood!",
167
                $argumentConfiguration
168
            ));
169
170 4
            $argument = $this->argumentFactory->createArgumentFromString(trim($argumentConfiguration));
171
        }
172
173 5
        return $argument->resolve();
174
    }
175
176
    /**
177
     * @return mixed
178
     */
179 4
    private function resolveParameterReflection(
180
        ReflectionParameter $parameterReflection,
181
        array $argumentsConfiguration,
182
        int $index
183
    ) {
184
        try {
185
            /** @var string $parameterName */
186 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...
187
188
            /** @var string|null $parameterTypeName */
189 4
            $parameterTypeName = $this->getTypeNameFromReflectionParameter($parameterReflection);
190
191
            /** @var Request|null $request */
192 4
            $request = $this->requestStack->getCurrentRequest();
193
194
            /** @var mixed $result */
195 4
            $result = null;
196
197 4
            if (isset($argumentsConfiguration[$parameterName])) {
198 1
                $result = $this->resolveArgumentConfiguration($argumentsConfiguration[$parameterName]);
199
200 3
            } elseif (array_key_exists($index, $argumentsConfiguration)) {
201 2
                $result = $this->resolveArgumentConfiguration($argumentsConfiguration[$index]);
202
203 2
            } elseif ($parameterTypeName === Request::class) {
204 1
                $result = $request;
205
206 1
            } elseif (is_object($request) && $request->get($parameterName)) {
207
                $result = $request->get($parameterName);
208
209
            } else {
210 1
                $result = $this->getDefaultValueFromParameterReflectionSafely($parameterReflection);
211
            }
212
213 3
            if (!empty($parameterTypeName) && (is_string($result) || is_int($result))) {
214
                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...
215
                    $result = call_user_func("{$parameterTypeName}::fromNative", $result);
216
217
                } elseif (class_exists($parameterTypeName)) {
218
                    $result = $this->controllerHelper->findEntity($parameterTypeName, (string)$result);
219
                }
220
            }
221
222 3
            return $result;
223
224 1
        } catch (Exception $exception) {
225 1
            throw new Exception(sprintf(
226 1
                "While resolving parameter-argument '%s' on call to '%s': %s",
227 1
                $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...
228 1
                $parameterReflection->getDeclaringFunction()->getName(),
0 ignored issues
show
Bug introduced by
Consider using $parameterReflection->ge...claringFunction()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
229 1
                $exception->getMessage()
230 1
            ), 0, $exception);
231
        }
232
    }
233
234
    /**
235
     * @return mixed
236
     */
237 1
    private function getDefaultValueFromParameterReflectionSafely(ReflectionParameter $parameterReflection)
238
    {
239 1
        if ($parameterReflection->getDeclaringFunction()->isInternal()) {
240
            return null;
241
        }
242
243
        try {
244 1
            return $parameterReflection->getDefaultValue();
245
246 1
        } catch (ReflectionException $exception) {
247
            /** @var string $parameterName */
248 1
            $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...
249
250
            /** @var ReflectionFunctionAbstract $routineReflection */
251 1
            $routineReflection = $parameterReflection->getDeclaringFunction();
252
253 1
            throw new InvalidArgumentException(sprintf(
254 1
                "Missing argument '%s' for the call to '%s'!",
255
                $parameterName,
256 1
                $routineReflection->getName()
257 1
            ), 0, $exception);
258
        }
259
    }
260
261 4
    private function getTypeNameFromReflectionParameter(ReflectionParameter $parameterReflection): ?string
262
    {
263
        /** @var string|null $parameterTypeName */
264 4
        $parameterTypeName = null;
265
266 4
        if ($parameterReflection->hasType()) {
267
            /** @var ReflectionType|ReflectionNamedType|null $parameterType */
268 1
            $parameterType = $parameterReflection->getType();
269
270 1
            if ($parameterType instanceof ReflectionNamedType) {
0 ignored issues
show
Bug introduced by
The class ReflectionNamedType 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...
271
                $parameterTypeName = $parameterType->getName();
272
273 1
            } elseif ($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...
274 1
                $parameterTypeName = $parameterType->__toString();
275
            }
276
        }
277
278 4
        return $parameterTypeName;
279
    }
280
281
}
282