Test Failed
Push — master ( a6cf2f...603a4c )
by Gerrit
04:07 queued 10s
created

ArgumentCompiler   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 208
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 94.74%

Importance

Changes 0
Metric Value
dl 0
loc 208
ccs 72
cts 76
cp 0.9474
rs 10
c 0
b 0
f 0
wmc 26
lcom 1
cbo 6

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A buildArguments() 0 19 3
A buildCallArguments() 0 30 4
B resolveArgumentConfiguration() 0 36 6
B resolveParameterReflection() 0 35 7
A getDefaultValueFromParameterReflectionSafely() 0 19 2
A getTypeNameFromReflectionParameter() 0 16 3
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 6
    public function buildArguments(
66
        array $argumentsConfiguration,
67
        array $additionalData = array()
68
    ): array {
69
        /** @var array $argumentValues */
70 6
        $argumentValues = array();
71
72 6
        foreach ($additionalData as $key => $value) {
73 3
            $this->argumentContext->set($key, $value);
74
        }
75
76 6
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
77
            /** @var array|string $argumentConfiguration */
78
79 5
            $argumentValues[$key] = $this->resolveArgumentConfiguration($argumentConfiguration);
80
        }
81
82 3
        return $argumentValues;
83
    }
84
85 5
    public function buildCallArguments(
86
        ReflectionFunctionAbstract $routineReflection,
87
        array $argumentsConfiguration,
88
        array $predefinedArguments = array(),
89
        array $additionalData = array()
90
    ): array {
91
        /** @var array<int, mixed> $callArguments */
92 5
        $callArguments = array();
93
94 5
        foreach ($additionalData as $key => $value) {
95 4
            $this->argumentContext->set($key, $value);
96
        }
97
98 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
99
            /** @var ReflectionParameter $parameterReflection */
100
101 4
            if (isset($predefinedArguments[$index])) {
102 2
                $callArguments[$index] = $predefinedArguments[$index];
103 2
                continue;
104
            }
105
106 4
            $callArguments[$index] = $this->resolveParameterReflection(
107 4
                $parameterReflection,
108 4
                $argumentsConfiguration,
109 4
                $index
110
            );
111
        }
112
113 4
        return $callArguments;
114
    }
115
116
    /**
117
     * @param array|string|bool|null $argumentConfiguration
118
     *
119
     * @return mixed
120
     */
121 8
    private function resolveArgumentConfiguration($argumentConfiguration)
122
    {
123 8
        Assert::oneOf(
124 8
            gettype($argumentConfiguration),
125 8
            ['string', 'array', 'NULL', 'boolean', 'object'],
126 8
            "Arguments must be defined as string, array, bool, object or null!"
127
        );
128
129
        /** @var Argument|null $argument */
130 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...
131
132 7
        if (is_bool($argumentConfiguration) || is_null($argumentConfiguration) || is_object($argumentConfiguration)) {
133
            return $argumentConfiguration;
134
135 7
        } else if ($argumentConfiguration === '') {
136
            return '';
137
138 7
        } elseif (is_array($argumentConfiguration)) {
139 2
            Assert::true($this->argumentFactory->understandsArray($argumentConfiguration), sprintf(
140 2
                "Argument '%s' could not be understood!",
141 2
                preg_replace("/\s+/is", "", var_export($argumentConfiguration, true))
142
            ));
143
144 1
            $argument = $this->argumentFactory->createArgumentFromArray($argumentConfiguration);
145
146
        } else {
147 5
            Assert::true($this->argumentFactory->understandsString($argumentConfiguration), sprintf(
148 5
                "Argument '%s' could not be understood!",
149 5
                $argumentConfiguration
150
            ));
151
152 4
            $argument = $this->argumentFactory->createArgumentFromString(trim($argumentConfiguration));
153
        }
154
155 5
        return $argument->resolve();
156
    }
157
158
    /**
159
     * @return mixed
160
     */
161 4
    private function resolveParameterReflection(
162
        ReflectionParameter $parameterReflection,
163
        array $argumentsConfiguration,
164
        int $index
165
    ) {
166
        /** @var string $parameterName */
167 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...
168
169
        /** @var string|null $parameterTypeName */
170 4
        $parameterTypeName = $this->getTypeNameFromReflectionParameter($parameterReflection);
171
172 4
        if (isset($argumentsConfiguration[$parameterName])) {
173
            /** @var mixed $value */
174 1
            $value = $this->resolveArgumentConfiguration($argumentsConfiguration[$parameterName]);
175
176 1
            if (!empty($parameterTypeName)) {
177
                if (class_exists($parameterTypeName) && is_scalar($value)) {
178
                    $value = $this->controllerHelper->findEntity($parameterTypeName, (string)$value);
179
                }
180
            }
181
182 1
            return $value;
183
184 3
        } elseif (array_key_exists($index, $argumentsConfiguration)) {
185 2
            return $this->resolveArgumentConfiguration($argumentsConfiguration[$index]);
186
187 2
        } elseif ($parameterTypeName === Request::class) {
188 1
            return $this->requestStack->getCurrentRequest();
189
190
        } else {
191 1
            return $this->getDefaultValueFromParameterReflectionSafely($parameterReflection);
192
        }
193
194
        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...
195
    }
196
197
    /**
198
     * @return mixed
199
     */
200 1
    private function getDefaultValueFromParameterReflectionSafely(ReflectionParameter $parameterReflection)
201
    {
202
        try {
203 1
            return $parameterReflection->getDefaultValue();
204
205 1
        } catch (ReflectionException $exception) {
206
            /** @var string $parameterName */
207 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...
208
209
            /** @var ReflectionFunctionAbstract $routineReflection */
210 1
            $routineReflection = $parameterReflection->getDeclaringFunction();
211
212 1
            throw new InvalidArgumentException(sprintf(
213 1
                "Missing argument '%s' for the call to '%s'!",
214 1
                $parameterName,
215 1
                $routineReflection->getName()
216
            ));
217
        }
218
    }
219
220 4
    private function getTypeNameFromReflectionParameter(ReflectionParameter $parameterReflection): ?string
221
    {
222
        /** @var string|null $parameterTypeName */
223 4
        $parameterTypeName = null;
224
225 4
        if ($parameterReflection->hasType()) {
226
            /** @var ReflectionType|null $parameterType */
227 1
            $parameterType = $parameterReflection->getType();
228
229 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...
230 1
                $parameterTypeName = $parameterType->__toString();
231
            }
232
        }
233
234 4
        return $parameterTypeName;
235
    }
236
237
}
238