Passed
Push — master ( 289469...3cf8a9 )
by Gerrit
03:42
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
30
final class ArgumentCompiler implements ArgumentCompilerInterface
31
{
32
33
    /**
34
     * @var ArgumentFactory
35
     */
36
    private $argumentFactory;
37
38
    /**
39
     * @var RequestStack
40
     */
41
    private $requestStack;
42
43
    /**
44
     * @var ArgumentContextInterface
45
     */
46
    private $argumentContext;
47
48
    /**
49
     * @var ControllerHelperInterface
50
     */
51
    private $controllerHelper;
52
53 11
    public function __construct(
54
        ArgumentFactory $argumentFactory,
55
        RequestStack $requestStack,
56
        ArgumentContextInterface $argumentContext,
57
        ControllerHelperInterface $controllerHelper
58
    ) {
59 11
        $this->argumentFactory = $argumentFactory;
60 11
        $this->requestStack = $requestStack;
61 11
        $this->argumentContext = $argumentContext;
62 11
        $this->controllerHelper = $controllerHelper;
63 11
    }
64
65
    public function understandsArgumentString(string $argumentConfiguration): bool
66
    {
67
        return $this->argumentFactory->understandsString($argumentConfiguration);
68
    }
69
70
    /** @param array|string $argumentConfiguration */
71
    public function buildArgument($argumentConfiguration, array $additionalData = array())
72
    {
73
        foreach ($additionalData as $key => $value) {
74
            $this->argumentContext->set($key, $value);
75
        }
76
77
        return $this->resolveArgumentConfiguration($argumentConfiguration);
78
    }
79
80 6
    public function buildArguments(
81
        array $argumentsConfiguration,
82
        array $additionalData = array()
83
    ): array {
84
        /** @var array $argumentValues */
85 6
        $argumentValues = array();
86
87 6
        foreach ($additionalData as $key => $value) {
88 3
            $this->argumentContext->set($key, $value);
89
        }
90
91 6
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
92
            /** @var array|string $argumentConfiguration */
93
94 5
            $argumentValues[$key] = $this->resolveArgumentConfiguration($argumentConfiguration);
95
        }
96
97 3
        return $argumentValues;
98
    }
99
100 5
    public function buildCallArguments(
101
        ReflectionFunctionAbstract $routineReflection,
102
        array $argumentsConfiguration,
103
        array $predefinedArguments = array(),
104
        array $additionalData = array()
105
    ): array {
106
        /** @var array<int, mixed> $callArguments */
107 5
        $callArguments = array();
108
109 5
        foreach ($additionalData as $key => $value) {
110 4
            $this->argumentContext->set($key, $value);
111
        }
112
113 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
114
            /** @var ReflectionParameter $parameterReflection */
115
116 4
            if (isset($predefinedArguments[$index])) {
117 2
                $callArguments[$index] = $predefinedArguments[$index];
118 2
                continue;
119
            }
120
121 4
            $callArguments[$index] = $this->resolveParameterReflection(
122 4
                $parameterReflection,
123 4
                $argumentsConfiguration,
124 4
                $index
125
            );
126
        }
127
128 4
        return $callArguments;
129
    }
130
131
    /**
132
     * @param array|string|bool|object|null $argumentConfiguration
133
     *
134
     * @return mixed
135
     */
136 8
    private function resolveArgumentConfiguration($argumentConfiguration)
137
    {
138 8
        Assert::oneOf(
139 8
            gettype($argumentConfiguration),
140 8
            ['string', 'array', 'NULL', 'boolean', 'object'],
141 8
            "Arguments must be defined as string, array, bool, object or null!"
142
        );
143
144
        /** @var Argument|null $argument */
145 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...
146
147 7
        if (is_bool($argumentConfiguration) || is_null($argumentConfiguration) || is_object($argumentConfiguration)) {
148
            return $argumentConfiguration;
149
150 7
        } else if ($argumentConfiguration === '') {
151
            return '';
152
153 7
        } elseif (is_array($argumentConfiguration)) {
154 2
            Assert::true($this->argumentFactory->understandsArray($argumentConfiguration), sprintf(
155 2
                "Argument '%s' could not be understood!",
156 2
                preg_replace("/\s+/is", "", var_export($argumentConfiguration, true))
157
            ));
158
159 1
            $argument = $this->argumentFactory->createArgumentFromArray($argumentConfiguration);
160
161
        } else {
162 5
            Assert::true($this->argumentFactory->understandsString($argumentConfiguration), sprintf(
163 5
                "Argument '%s' could not be understood!",
164 5
                $argumentConfiguration
165
            ));
166
167 4
            $argument = $this->argumentFactory->createArgumentFromString(trim($argumentConfiguration));
168
        }
169
170 5
        return $argument->resolve();
171
    }
172
173
    /**
174
     * @return mixed
175
     */
176 4
    private function resolveParameterReflection(
177
        ReflectionParameter $parameterReflection,
178
        array $argumentsConfiguration,
179
        int $index
180
    ) {
181
        /** @var string $parameterName */
182 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...
183
184
        /** @var string|null $parameterTypeName */
185 4
        $parameterTypeName = $this->getTypeNameFromReflectionParameter($parameterReflection);
186
187 4
        if (isset($argumentsConfiguration[$parameterName])) {
188
            /** @var mixed $value */
189 1
            $value = $this->resolveArgumentConfiguration($argumentsConfiguration[$parameterName]);
190
191 1
            if (!empty($parameterTypeName)) {
192
                if (class_exists($parameterTypeName) && is_scalar($value)) {
193
                    $value = $this->controllerHelper->findEntity($parameterTypeName, (string)$value);
194
                }
195
            }
196
197 1
            return $value;
198
199 3
        } elseif (array_key_exists($index, $argumentsConfiguration)) {
200 2
            return $this->resolveArgumentConfiguration($argumentsConfiguration[$index]);
201
202 2
        } elseif ($parameterTypeName === Request::class) {
203 1
            return $this->requestStack->getCurrentRequest();
204
205
        } else {
206 1
            return $this->getDefaultValueFromParameterReflectionSafely($parameterReflection);
207
        }
208
209
        return null;
0 ignored issues
show
Unused Code introduced by
return null; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
210
    }
211
212
    /**
213
     * @return mixed
214
     */
215 1
    private function getDefaultValueFromParameterReflectionSafely(ReflectionParameter $parameterReflection)
216
    {
217
        try {
218 1
            return $parameterReflection->getDefaultValue();
219
220 1
        } catch (ReflectionException $exception) {
221
            /** @var string $parameterName */
222 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...
223
224
            /** @var ReflectionFunctionAbstract $routineReflection */
225 1
            $routineReflection = $parameterReflection->getDeclaringFunction();
226
227 1
            throw new InvalidArgumentException(sprintf(
228 1
                "Missing argument '%s' for the call to '%s'!",
229 1
                $parameterName,
230 1
                $routineReflection->getName()
231
            ));
232
        }
233
    }
234
235 4
    private function getTypeNameFromReflectionParameter(ReflectionParameter $parameterReflection): ?string
236
    {
237
        /** @var string|null $parameterTypeName */
238 4
        $parameterTypeName = null;
239
240 4
        if ($parameterReflection->hasType()) {
241
            /** @var ReflectionType|null $parameterType */
242 1
            $parameterType = $parameterReflection->getType();
243
244 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...
245 1
                $parameterTypeName = $parameterType->__toString();
246
            }
247
        }
248
249 4
        return $parameterTypeName;
250
    }
251
252
}
253