Passed
Push — master ( 621a9a...2a422d )
by Gerrit
05:17
created

ArgumentCompiler::understandsArgumentString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
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 ReflectionFunctionAbstract;
23
use ReflectionParameter;
24
use ReflectionException;
25
use Symfony\Component\HttpFoundation\RequestStack;
26
use Addiks\SymfonyGenerics\Arguments\ArgumentContextInterface;
27
use InvalidArgumentException;
28
use Addiks\SymfonyGenerics\Controllers\ControllerHelperInterface;
29
use ValueObjects\ValueObjectInterface;
30
use Exception;
31
32
final class ArgumentCompiler implements ArgumentCompilerInterface
33
{
34
35
    /**
36
     * @var ArgumentFactory
37
     */
38
    private $argumentFactory;
39
40
    /**
41
     * @var RequestStack
42
     */
43
    private $requestStack;
44
45
    /**
46
     * @var ArgumentContextInterface
47
     */
48
    private $argumentContext;
49
50
    /**
51
     * @var ControllerHelperInterface
52
     */
53
    private $controllerHelper;
54
55 11
    public function __construct(
56
        ArgumentFactory $argumentFactory,
57
        RequestStack $requestStack,
58
        ArgumentContextInterface $argumentContext,
59
        ControllerHelperInterface $controllerHelper
60
    ) {
61 11
        $this->argumentFactory = $argumentFactory;
62 11
        $this->requestStack = $requestStack;
63 11
        $this->argumentContext = $argumentContext;
64 11
        $this->controllerHelper = $controllerHelper;
65 11
    }
66
67
    public function understandsArgumentString(string $argumentConfiguration): bool
68
    {
69
        return $this->argumentFactory->understandsString($argumentConfiguration);
70
    }
71
72
    /** @param array|string $argumentConfiguration */
73
    public function buildArgument($argumentConfiguration, array $additionalData = array())
74
    {
75
        foreach ($additionalData as $key => $value) {
76
            $this->argumentContext->set($key, $value);
77
        }
78
79
        return $this->resolveArgumentConfiguration($argumentConfiguration);
80
    }
81
82 6
    public function buildArguments(
83
        array $argumentsConfiguration,
84
        array $additionalData = array()
85
    ): array {
86
        /** @var array $argumentValues */
87 6
        $argumentValues = array();
88
89 6
        foreach ($additionalData as $key => $value) {
90 3
            $this->argumentContext->set($key, $value);
91
        }
92
93 6
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
94
            /** @var array|string $argumentConfiguration */
95
96 5
            $argumentValues[$key] = $this->resolveArgumentConfiguration($argumentConfiguration);
97
        }
98
99 3
        return $argumentValues;
100
    }
101
102 5
    public function buildCallArguments(
103
        ReflectionFunctionAbstract $routineReflection,
104
        array $argumentsConfiguration,
105
        array $predefinedArguments = array(),
106
        array $additionalData = array()
107
    ): array {
108
        /** @var array<int, mixed> $callArguments */
109 5
        $callArguments = array();
110
111 5
        foreach ($additionalData as $key => $value) {
112 4
            $this->argumentContext->set($key, $value);
113
        }
114
115 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
116
            /** @var ReflectionParameter $parameterReflection */
117
118 4
            if (isset($predefinedArguments[$index])) {
119 2
                $callArguments[$index] = $predefinedArguments[$index];
120 2
                continue;
121
            }
122
123 4
            $callArguments[$index] = $this->resolveParameterReflection(
124 4
                $parameterReflection,
125 4
                $argumentsConfiguration,
126 4
                $index
127
            );
128
        }
129
130 4
        return $callArguments;
131
    }
132
133
    /**
134
     * @param array|string|bool|object|null $argumentConfiguration
135
     *
136
     * @return mixed
137
     */
138 8
    private function resolveArgumentConfiguration($argumentConfiguration)
139
    {
140 8
        Assert::oneOf(
141 8
            gettype($argumentConfiguration),
142 8
            ['string', 'array', 'NULL', 'boolean', 'object'],
143 8
            "Arguments must be defined as string, array, bool, object or null!"
144
        );
145
146
        /** @var Argument|null $argument */
147 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...
148
149 7
        if (is_bool($argumentConfiguration) || is_null($argumentConfiguration) || is_object($argumentConfiguration)) {
150
            return $argumentConfiguration;
151
152 7
        } else if ($argumentConfiguration === '') {
153
            return '';
154
155 7
        } elseif (is_array($argumentConfiguration)) {
156 2
            Assert::true($this->argumentFactory->understandsArray($argumentConfiguration), sprintf(
157 2
                "Argument '%s' could not be understood!",
158 2
                preg_replace("/\s+/is", "", var_export($argumentConfiguration, true))
159
            ));
160
161 1
            $argument = $this->argumentFactory->createArgumentFromArray($argumentConfiguration);
162
163
        } else {
164 5
            Assert::true($this->argumentFactory->understandsString($argumentConfiguration), sprintf(
165 5
                "Argument '%s' could not be understood!",
166 5
                $argumentConfiguration
167
            ));
168
169 4
            $argument = $this->argumentFactory->createArgumentFromString(trim($argumentConfiguration));
170
        }
171
172 5
        return $argument->resolve();
173
    }
174
175
    /**
176
     * @return mixed
177
     */
178 4
    private function resolveParameterReflection(
179
        ReflectionParameter $parameterReflection,
180
        array $argumentsConfiguration,
181
        int $index
182
    ) {
183
        try {
184
            /** @var string $parameterName */
185 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...
186
187
            /** @var string|null $parameterTypeName */
188 4
            $parameterTypeName = $this->getTypeNameFromReflectionParameter($parameterReflection);
189
190
            /** @var Request|null $request */
191 4
            $request = $this->requestStack->getCurrentRequest();
192
193
            /** @var mixed $result */
194 4
            $result = null;
195
196 4
            if (isset($argumentsConfiguration[$parameterName])) {
197 1
                $result = $this->resolveArgumentConfiguration($argumentsConfiguration[$parameterName]);
198
199 3
            } elseif (array_key_exists($index, $argumentsConfiguration)) {
200 2
                $result = $this->resolveArgumentConfiguration($argumentsConfiguration[$index]);
201
202 2
            } elseif ($parameterTypeName === Request::class) {
203 1
                $result = $request;
204
205 1
            } elseif (is_object($request) && $request->get($parameterName)) {
206
                $result = $request->get($parameterName);
207
208
            } else {
209 1
                $result = $this->getDefaultValueFromParameterReflectionSafely($parameterReflection);
210
            }
211
212 3
            if (!empty($parameterTypeName) && (is_string($result) || is_int($result))) {
213
                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...
214
                    $result = call_user_func("{$parameterTypeName}::fromNative", $result);
215
216
                } elseif (class_exists($parameterTypeName)) {
217
                    $result = $this->controllerHelper->findEntity($parameterTypeName, (string)$result);
218
                }
219
            }
220
221 3
            return $result;
222
223 1
        } catch (Exception $exception) {
224 1
            throw new Exception(sprintf(
225 1
                "While resolving parameter-argument '%s' on call to '%s': %s",
226 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...
227 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...
228 1
                $exception->getMessage()
229 1
            ), 0, $exception);
230
        }
231
    }
232
233
    /**
234
     * @return mixed
235
     */
236 1
    private function getDefaultValueFromParameterReflectionSafely(ReflectionParameter $parameterReflection)
237
    {
238 1
        if ($parameterReflection->getDeclaringFunction()->isInternal()) {
239
            return null;
240
        }
241
242
        try {
243 1
            return $parameterReflection->getDefaultValue();
244
245 1
        } catch (ReflectionException $exception) {
246
            /** @var string $parameterName */
247 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...
248
249
            /** @var ReflectionFunctionAbstract $routineReflection */
250 1
            $routineReflection = $parameterReflection->getDeclaringFunction();
251
252 1
            throw new InvalidArgumentException(sprintf(
253 1
                "Missing argument '%s' for the call to '%s'!",
254 1
                $parameterName,
255 1
                $routineReflection->getName()
256 1
            ), 0, $exception);
257
        }
258
    }
259
260 4
    private function getTypeNameFromReflectionParameter(ReflectionParameter $parameterReflection): ?string
261
    {
262
        /** @var string|null $parameterTypeName */
263 4
        $parameterTypeName = null;
264
265 4
        if ($parameterReflection->hasType()) {
266
            /** @var ReflectionType|null $parameterType */
267 1
            $parameterType = $parameterReflection->getType();
268
269 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...
270 1
                $parameterTypeName = $parameterType->__toString();
271
            }
272
        }
273
274 4
        return $parameterTypeName;
275
    }
276
277
}
278