Invoker::prepareActualParameters()   C
last analyzed

Complexity

Conditions 14
Paths 12

Size

Total Lines 59
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 59
rs 6.4055
c 0
b 0
f 0
cc 14
eloc 30
nc 12
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpJsonRpc\Server;
4
5
use PhpJsonRpc\Common\TypeAdapter\TypeAdapter;
6
use PhpJsonRpc\Common\TypeAdapter\TypeCastException;
7
use PhpJsonRpc\Error\InvalidParamsException;
8
use PhpJsonRpc\Error\ServerErrorException;
9
10
/**
11
 * Invoker of methods
12
 */
13
class Invoker
14
{
15
    /**
16
     * @var TypeAdapter
17
     */
18
    private $typeAdapter;
19
20
    /**
21
     * Invoker constructor.
22
     */
23
    public function __construct()
24
    {
25
        $this->typeAdapter = new TypeAdapter();
26
    }
27
28
    /**
29
     * @param mixed  $object
30
     * @param string $method
31
     * @param array  $parameters
32
     *
33
     * @return mixed
34
     */
35
    public function invoke($object, string $method, array $parameters)
36
    {
37
        $handler    = $object;
38
        $reflection = new \ReflectionMethod($handler, $method);
39
40
        if ($reflection->getNumberOfRequiredParameters() > count($parameters)) {
41
            throw new InvalidParamsException('Expected ' . $reflection->getNumberOfRequiredParameters() . ' parameters');
42
        }
43
44
        $formalParameters = $reflection->getParameters();
45
        $actualParameters = $this->prepareActualParameters($formalParameters, $parameters);
46
47
        try {
48
            $result = $reflection->invokeArgs($handler, $actualParameters);
49
        } catch (\Exception $exception) {
50
            throw new ServerErrorException($exception->getMessage(), $exception->getCode());
51
        }
52
53
        return $this->castResult($result);
54
    }
55
56
    /**
57
     * @return TypeAdapter
58
     */
59
    public function getTypeAdapter(): TypeAdapter
60
    {
61
        return $this->typeAdapter;
62
    }
63
64
    /**
65
     * @param \ReflectionParameter[] $formalParameters Formal parameters
66
     * @param array                  $parameters       Parameters from request (raw)
67
     *
68
     * @return array
69
     */
70
    private function prepareActualParameters(array $formalParameters, array $parameters): array
71
    {
72
        $result = [];
73
74
        // Handle named parameters
75
        if ($this->isNamedArray($parameters)) {
76
77
            foreach ($formalParameters as $formalParameter) {
78
                /** @var \ReflectionParameter $formalParameter */
79
80
                $formalType = (string) $formalParameter->getType();
81
                $name       = $formalParameter->name;
82
83
                if ($formalParameter->isOptional()) {
84
                    if (!array_key_exists($name, $parameters)) {
85
                        $result[$name] = $formalParameter->getDefaultValue();
86
                        continue;
87
                    }
88
89
                    if ($formalParameter->allowsNull() && $parameters[$name] === null) {
90
                        $result[$name] = $parameters[$name];
91
                        continue;
92
                    }
93
94
                    $result[$name] = $formalParameter->getClass() !== null
95
                        ? $this->toObject($formalParameter->getClass()->name, $parameters[$name])
96
                        : $this->matchType($formalType, $parameters[$name]);
97
                }
98
99
                if (!array_key_exists($name, $parameters)) {
100
                    throw new InvalidParamsException('Named parameter error');
101
                }
102
103
                $result[$name] = $this->matchType($formalType, $parameters[$name]);
104
            }
105
106
            return $result;
107
        }
108
109
        // Handle positional parameters
110
        foreach ($formalParameters as $position => $formalParameter) {
111
            /** @var \ReflectionParameter $formalParameter */
112
113
            if ($formalParameter->isOptional() && !isset($parameters[$position])) {
114
                break;
115
            }
116
117
            if (!isset($parameters[$position])) {
118
                throw new InvalidParamsException('Positional parameter error');
119
            }
120
121
            $formalType = (string) $formalParameter->getType();
122
            $result[] = $formalParameter->getClass() !== null
123
                ? $this->toObject($formalParameter->getClass()->name, $parameters[$position])
124
                : $this->matchType($formalType, $parameters[$position]);
125
        }
126
127
        return $result;
128
    }
129
130
    /**
131
     * @param array $rawParameters
132
     *
133
     * @return bool
134
     */
135
    private function isNamedArray(array $rawParameters): bool
136
    {
137
        return array_keys($rawParameters) !== range(0, count($rawParameters) - 1);
138
    }
139
140
    /**
141
     * @param string $formalType
142
     * @param mixed  $value
143
     *
144
     * @return mixed
145
     */
146
    private function matchType(string $formalType, $value)
147
    {
148
        // Parameter without type-hinting returns as is
149
        if ($formalType === '') {
150
            return $value;
151
        }
152
153
        if ($this->isType($formalType, $value)) {
154
            return $value;
155
        }
156
157
        throw new InvalidParamsException('Type hint failed');
158
    }
159
160
    /**
161
     * @param string $formalClass
162
     * @param mixed  $value
163
     *
164
     * @return mixed
165
     */
166
    private function toObject(string $formalClass, $value)
167
    {
168
        if ($this->typeAdapter->isClassRegistered($formalClass)) {
169
            try {
170
                return $this->typeAdapter->toObject($value);
171
            } catch (TypeCastException $exception) {
172
                throw new InvalidParamsException('Class cast failed');
173
            }
174
        }
175
176
        throw new InvalidParamsException('Class hint failed');
177
    }
178
179
    /**
180
     * @param string $type
181
     * @param $value
182
     *
183
     * @return bool
184
     */
185
    private function isType(string $type, $value)
186
    {
187
        switch ($type) {
188
            case 'bool':
189
                return is_bool($value);
190
191
            case 'int':
192
                return is_int($value);
193
194
            case 'float':
195
                return is_float($value) || is_int($value);
196
197
            case 'string':
198
                return is_string($value);
199
200
            case 'array':
201
                return is_array($value);
202
203
            default:
204
                throw new InvalidParamsException('Type match error');
205
        }
206
    }
207
208
    /**
209
     * @param $result
210
     *
211
     * @return mixed
212
     */
213
    private function castResult($result)
214
    {
215
        try {
216
            $result = $this->typeAdapter->toArray($result);
217
        } catch (TypeCastException $exception) {
218
            return $result;
219
        }
220
221
        return $result;
222
    }
223
}
224