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