Completed
Push — master ( 8ec7ac...21563f )
by Denis
02:14
created

RequestParser::onPreParse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace PhpJsonRpc\Server;
4
5
use PhpJsonRpc\Common\Chain\Chain;
6
use PhpJsonRpc\Common\Chain\Container;
7
use PhpJsonRpc\Core\Call\AbstractCall;
8
use PhpJsonRpc\Core\Call\CallUnit;
9
use PhpJsonRpc\Core\Call\CallError;
10
use PhpJsonRpc\Core\Call\CallNotification;
11
use PhpJsonRpc\Core\CallSpecifier;
12
use PhpJsonRpc\Error\InvalidRequestException;
13
use PhpJsonRpc\Error\ParseErrorException;
14
15
/**
16
 * Request parser
17
 */
18
class RequestParser
19
{
20
    /**
21
     * @var Chain
22
     */
23
    private $preParse;
24
25
    /**
26
     * @var Chain
27
     */
28
    private $postParse;
29
30
    /**
31
     * RequestParser constructor.
32
     */
33
    public function __construct()
34
    {
35
        $this->preParse  = Chain::createBase();
36
        $this->postParse = Chain::createBase();
37
    }
38
39
    /**
40
     * Parse request data
41
     *
42
     * @param string $data
43
     *
44
     * @return CallSpecifier
45
     */
46
    public function parse(string $data): CallSpecifier
47
    {
48
        $payload = @json_decode($data, true);
49
50
        if (!is_array($payload)) {
51
            return new CallSpecifier([new CallError(new ParseErrorException())], true);
52
        }
53
54
        $units = [];
55
56
        // Single request
57
        if ($this->isSingleRequest($payload)) {
58
            $units[] = $this->decodeCall($payload);
59
            return new CallSpecifier($units, true);
60
        }
61
62
        // Batch request
63
        /** @var array $payload */
64
        foreach ($payload as $record) {
65
            $units[] = $this->decodeCall($record);
66
        }
67
68
        return new CallSpecifier($units, false);
69
    }
70
71
    /**
72
     * Get pre-parse chain
73
     *
74
     * @return Chain
75
     */
76
    public function onPreParse(): Chain
77
    {
78
        return $this->preParse;
79
    }
80
81
    /**
82
     * Get post-parse chain
83
     *
84
     * @return Chain
85
     */
86
    public function onPostParse(): Chain
87
    {
88
        return $this->postParse;
89
    }
90
91
    /**
92
     * @param $record
93
     *
94
     * @return AbstractCall
95
     */
96
    private function decodeCall($record): AbstractCall
97
    {
98
        $record = $this->preParse($record);
99
100
        if ($this->isValidCall($record)) {
101
            $unit = new CallUnit($record['id'], $record['method'], $record['params'] ?? []);
102
        } elseif ($this->isValidNotification($record)) {
103
            $unit = new CallNotification($record['method'], $record['params']);
104
        } else {
105
            $unit = new CallError(new InvalidRequestException());
106
        }
107
108
        return $this->postParse($unit);
109
    }
110
111
    /**
112
     * @param array $payload
113
     *
114
     * @return bool
115
     */
116
    private function isSingleRequest(array $payload): bool
117
    {
118
        return array_keys($payload) !== range(0, count($payload) - 1);
119
    }
120
121
    /**
122
     * @param array $payload
123
     *
124
     * @return bool
125
     */
126 View Code Duplication
    private function isValidCall($payload): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
    {
128
        if (!is_array($payload)) {
129
            return false;
130
        }
131
132
        $headerValid = array_key_exists('jsonrpc', $payload) && $payload['jsonrpc'] === '2.0';
133
        $methodValid = array_key_exists('method', $payload)  && is_string($payload['method']);
134
        $idValid     = array_key_exists('id', $payload);
135
136
        // This member MAY be omitted
137
        $paramsValid = true;
138
        if (array_key_exists('params', $payload) && !is_array($payload['params'])) {
139
            $paramsValid = false;
140
        }
141
142
        return $headerValid && $methodValid && $paramsValid && $idValid;
143
    }
144
145
    /**
146
     * @param array $payload
147
     *
148
     * @return bool
149
     */
150 View Code Duplication
    private function isValidNotification($payload): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    {
152
        if (!is_array($payload)) {
153
            return false;
154
        }
155
156
        $headerValid = array_key_exists('jsonrpc', $payload) && $payload['jsonrpc'] === '2.0';
157
        $methodValid = array_key_exists('method', $payload)  && is_string($payload['method']);
158
        $idValid     = !array_key_exists('id', $payload);
159
160
        // This member MAY be omitted
161
        $paramsValid = true;
162
        if (array_key_exists('params', $payload) && !is_array($payload['params'])) {
163
            $paramsValid = false;
164
        }
165
166
        return $headerValid && $methodValid && $paramsValid && $idValid;
167
    }
168
169
    /**
170
     * @param mixed $record
171
     *
172
     * @return mixed
173
     */
174
    private function preParse($record)
175
    {
176
        return $this->preParse->handle(new Container($this, $record))->last();
177
    }
178
179
    /**
180
     * @param AbstractCall $result
181
     *
182
     * @return AbstractCall
183
     */
184
    private function postParse(AbstractCall $result): AbstractCall
185
    {
186
        return $this->postParse->handle(new Container($this, $result))->last();
187
    }
188
}
189