Passed
Push — master ( 5d273a...1f622e )
by Gerrit
10:11
created

ArgumentCompiler   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 98.57%

Importance

Changes 0
Metric Value
dl 0
loc 192
ccs 69
cts 70
cp 0.9857
rs 10
c 0
b 0
f 0
wmc 21
lcom 1
cbo 5

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A buildArguments() 0 21 3
A buildCallArguments() 0 32 4
A resolveArgumentConfiguration() 0 32 4
A resolveParameterReflection() 0 26 4
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
29
final class ArgumentCompiler implements ArgumentCompilerInterface
30
{
31
32
    /**
33
     * @var ArgumentFactory
34
     */
35
    private $argumentFactory;
36
37
    /**
38
     * @var RequestStack
39
     */
40
    private $requestStack;
41
42
    /**
43
     * @var ArgumentContextInterface
44
     */
45
    private $argumentContext;
46
47 11
    public function __construct(
48
        ArgumentFactory $argumentFactory,
49
        RequestStack $requestStack,
50
        ArgumentContextInterface $argumentContext
51
    ) {
52 11
        $this->argumentFactory = $argumentFactory;
53 11
        $this->requestStack = $requestStack;
54 11
        $this->argumentContext = $argumentContext;
55 11
    }
56
57 6
    public function buildArguments(
58
        array $argumentsConfiguration,
59
        Request $request,
60
        array $additionalData = array()
61
    ): array {
62
        /** @var array $argumentValues */
63 6
        $argumentValues = array();
64
65 6
        $this->argumentContext->clear();
66 6
        foreach ($additionalData as $key => $value) {
67 3
            $this->argumentContext->set($key, $value);
68
        }
69
70 6
        foreach ($argumentsConfiguration as $key => $argumentConfiguration) {
71
            /** @var array|string $argumentConfiguration */
72
73 5
            $argumentValues[$key] = $this->resolveArgumentConfiguration($argumentConfiguration);
74
        }
75
76 3
        return $argumentValues;
77
    }
78
79 5
    public function buildCallArguments(
80
        ReflectionFunctionAbstract $routineReflection,
81
        array $argumentsConfiguration,
82
        Request $request,
83
        array $predefinedArguments = array(),
84
        array $additionalData = array()
85
    ): array {
86
        /** @var array<int, mixed> $callArguments */
87 5
        $callArguments = array();
88
89 5
        $this->argumentContext->clear();
90 5
        foreach ($additionalData as $key => $value) {
91 4
            $this->argumentContext->set($key, $value);
92
        }
93
94 5
        foreach ($routineReflection->getParameters() as $index => $parameterReflection) {
95
            /** @var ReflectionParameter $parameterReflection */
96
97 4
            if (isset($predefinedArguments[$index])) {
98 2
                $callArguments[$index] = $predefinedArguments[$index];
99 2
                continue;
100
            }
101
102 4
            $callArguments[$index] = $this->resolveParameterReflection(
103 4
                $parameterReflection,
104 4
                $argumentsConfiguration,
105 4
                $index
106
            );
107
        }
108
109 4
        return $callArguments;
110
    }
111
112
    /**
113
     * @param array|string $argumentConfiguration
114
     *
115
     * @return mixed
116
     */
117 8
    private function resolveArgumentConfiguration($argumentConfiguration)
118
    {
119 8
        Assert::true(
120 8
            is_array($argumentConfiguration) || is_string($argumentConfiguration),
121 8
            "Arguments must be defined as string or array!"
122
        );
123
124
        /** @var Argument|null $argument */
125 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...
126
127 7
        if ($argumentConfiguration === '') {
128
            return '';
129
130 7
        } elseif (is_array($argumentConfiguration)) {
131 2
            Assert::true($this->argumentFactory->understandsArray($argumentConfiguration), sprintf(
132 2
                "Argument '%s' could not be understood!",
133 2
                preg_replace("/\s+/is", "", var_export($argumentConfiguration, true))
134
            ));
135
136 1
            $argument = $this->argumentFactory->createArgumentFromArray($argumentConfiguration);
137
138
        } else {
139 5
            Assert::true($this->argumentFactory->understandsString($argumentConfiguration), sprintf(
140 5
                "Argument '%s' could not be understood!",
141 5
                $argumentConfiguration
142
            ));
143
144 4
            $argument = $this->argumentFactory->createArgumentFromString(trim($argumentConfiguration));
145
        }
146
147 5
        return $argument->resolve();
148
    }
149
150
    /**
151
     * @return mixed
152
     */
153 4
    private function resolveParameterReflection(
154
        ReflectionParameter $parameterReflection,
155
        array $argumentsConfiguration,
156
        int $index
157
    ) {
158
        /** @var string $parameterName */
159 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...
160
161
        /** @var string|null $parameterTypeName */
162 4
        $parameterTypeName = $this->getTypeNameFromReflectionParameter($parameterReflection);
163
164 4
        if (isset($argumentsConfiguration[$parameterName])) {
165 1
            return $this->resolveArgumentConfiguration($argumentsConfiguration[$parameterName]);
166
167 3
        } elseif (isset($argumentsConfiguration[$index])) {
168 2
            return $this->resolveArgumentConfiguration($argumentsConfiguration[$index]);
169
170 2
        } elseif ($parameterTypeName === Request::class) {
171 1
            return $this->requestStack->getCurrentRequest();
172
173
        } else {
174 1
            return $this->getDefaultValueFromParameterReflectionSafely($parameterReflection);
175
        }
176
177
        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...
178
    }
179
180
    /**
181
     * @return mixed
182
     */
183 1
    private function getDefaultValueFromParameterReflectionSafely(ReflectionParameter $parameterReflection)
184
    {
185
        try {
186 1
            return $parameterReflection->getDefaultValue();
187
188 1
        } catch (ReflectionException $exception) {
189
            /** @var string $parameterName */
190 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...
191
192
            /** @var ReflectionFunctionAbstract $routineReflection */
193 1
            $routineReflection = $parameterReflection->getDeclaringFunction();
194
195 1
            throw new InvalidArgumentException(sprintf(
196 1
                "Missing argument '%s' for the call to '%s'!",
197 1
                $parameterName,
198 1
                $routineReflection->getName()
199
            ));
200
        }
201
    }
202
203 4
    private function getTypeNameFromReflectionParameter(ReflectionParameter $parameterReflection): ?string
204
    {
205
        /** @var string|null $parameterTypeName */
206 4
        $parameterTypeName = null;
207
208 4
        if ($parameterReflection->hasType()) {
209
            /** @var ReflectionType|null $parameterType */
210 1
            $parameterType = $parameterReflection->getType();
211
212 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...
213 1
                $parameterTypeName = $parameterType->__toString();
214
            }
215
        }
216
217 4
        return $parameterTypeName;
218
    }
219
220
}
221