Completed
Push — master ( 21563f...50178b )
by Denis
02:07
created

Invoker::invoke()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 12
nc 3
nop 3
1
<?php
2
3
namespace PhpJsonRpc\Server;
4
5
use PhpJsonRpc\Error\InvalidParamsException;
6
use PhpJsonRpc\Error\ServerErrorException;
7
8
/**
9
 * Invoker of methods
10
 */
11
class Invoker
12
{
13
    /**
14
     * @param mixed  $object
15
     * @param string $method
16
     * @param array  $parameters
17
     *
18
     * @return mixed
19
     */
20
    public function invoke($object, string $method, array $parameters)
21
    {
22
        $handler    = $object;
23
        $reflection = new \ReflectionMethod($handler, $method);
24
25
        if ($reflection->getNumberOfRequiredParameters() > count($parameters)) {
26
            throw new InvalidParamsException('Expected ' . $reflection->getNumberOfRequiredParameters() . ' parameters');
27
        }
28
29
        $formalParameters = $reflection->getParameters();
30
        $actualParameters = $this->prepareActualParameters($formalParameters, $parameters);
31
32
        try {
33
            $result = $reflection->invokeArgs($handler, $actualParameters);
34
        } catch (\Exception $exception) {
35
            throw new ServerErrorException($exception->getMessage(), $exception->getCode());
36
        }
37
38
        return $result;
39
    }
40
41
    /**
42
     * @param \ReflectionParameter[] $formalParameters Formal parameters
43
     * @param array                  $parameters       Parameters from request (raw)
44
     *
45
     * @return array
46
     */
47
    private function prepareActualParameters(array $formalParameters, array $parameters): array
48
    {
49
        $result = [];
50
51
        // Handle named parameters
52
        if ($this->isNamedParameters($parameters)) {
53
54
            foreach ($formalParameters as $formalParameter) {
55
                /** @var \ReflectionParameter $formalParameter */
56
57
                $formalType = (string) $formalParameter->getType();
58
                $name       = $formalParameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $formalParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
59
60
                if ($formalParameter->isOptional()) {
61
                    if (array_key_exists($name, $parameters)) {
62
                        $result[$name] = $this->matchType($formalType, $parameters[$name]);
63
                    } else {
64
                        continue;
65
                    }
66
                }
67
68
                if (!array_key_exists($name, $parameters)) {
69
                    throw new InvalidParamsException('Named parameter error');
70
                }
71
72
                $result[$name] = $this->matchType($formalType, $parameters[$name]);
73
            }
74
75
            return $result;
76
        }
77
78
        // Handle positional parameters
79
        for ($position = 0; $position < count($formalParameters); $position++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
80
            /** @var \ReflectionParameter $formalParameter */
81
            $formalParameter = $formalParameters[$position];
82
83
            if ($formalParameter->isOptional() && !isset($parameters[$position])) {
84
                break;
85
            }
86
87
            if (!isset($parameters[$position])) {
88
                throw new InvalidParamsException('Positional parameter error');
89
            }
90
91
            $formalType = (string) $formalParameter->getType();
92
            $result[] = $this->matchType($formalType, $parameters[$position]);
93
        }
94
95
        return $result;
96
    }
97
98
    /**
99
     * @param array $rawParameters
100
     *
101
     * @return bool
102
     */
103
    private function isNamedParameters(array $rawParameters): bool
104
    {
105
        return array_keys($rawParameters) !== range(0, count($rawParameters) - 1);
106
    }
107
108
    /**
109
     * @param string $formalType
110
     * @param mixed  $value
111
     *
112
     * @return mixed
113
     */
114
    private function matchType($formalType, $value)
115
    {
116
        // Parameter without type-hinting returns as is
117
        if ($formalType === '') {
118
            return $value;
119
        }
120
121
        if ($this->isType($formalType, $value)) {
122
            return $value;
123
        }
124
125
        throw new InvalidParamsException('Match type failed');
126
    }
127
128
    /**
129
     * @param string $type
130
     * @param $value
131
     *
132
     * @return bool
133
     */
134
    private function isType(string $type, $value)
135
    {
136
        switch ($type) {
137
            case 'bool':
138
                return is_bool($value);
139
140
            case 'int':
141
                return is_int($value);
142
143
            case 'float':
144
                return is_float($value) || is_int($value);
145
146
            case 'string':
147
                return is_string($value);
148
149
            case 'array':
150
                return is_array($value);
151
152
            default:
153
                throw new InvalidParamsException('Type match error');
154
        }
155
    }
156
}
157