Client   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 14
dl 0
loc 251
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getRequestBuilder() 0 4 1
A getTransport() 0 4 1
A setTransport() 0 4 1
A getResponseParser() 0 4 1
A setResponseParser() 0 4 1
A getGeneratorId() 0 4 1
A setIdGenerator() 0 4 1
C call() 0 32 7
A notification() 0 15 3
A batch() 0 6 1
C batchExecute() 0 34 7
A execute() 0 7 1
1
<?php
2
3
namespace PhpJsonRpc;
4
5
use PhpJsonRpc\Client\IdGenerator;
6
use PhpJsonRpc\Client\IdGeneratorInterface;
7
use PhpJsonRpc\Core\Result\Error;
8
use PhpJsonRpc\Error\InvalidResponseException;
9
use PhpJsonRpc\Client\HttpTransport;
10
use PhpJsonRpc\Client\AbstractTransport;
11
use PhpJsonRpc\Client\RequestBuilder;
12
use PhpJsonRpc\Client\ResponseParser;
13
use PhpJsonRpc\Core\Invoke\AbstractInvoke;
14
use PhpJsonRpc\Core\Invoke\Notification;
15
use PhpJsonRpc\Core\Invoke\Invoke;
16
use PhpJsonRpc\Core\InvokeSpec;
17
use PhpJsonRpc\Core\Result\Result;
18
use PhpJsonRpc\Core\ResultSpec;
19
use PhpJsonRpc\Error\JsonRpcException;
20
21
/**
22
 * Implementation of JSON-RPC2 client specification
23
 *
24
 * @link http://www.jsonrpc.org/specification
25
 */
26
class Client
27
{
28
    /**
29
     * Client will return null if server error happened
30
     */
31
    const ERRMODE_SILENT    = 0;
32
33
    /**
34
     * Client will throw exception if server error happened
35
     */
36
    const ERRMODE_EXCEPTION = 2;
37
38
    /**
39
     * @var AbstractInvoke[]
40
     */
41
    private $units = [];
42
43
    /**
44
     * @var bool
45
     */
46
    private $isSingleRequest = true;
47
48
    /**
49
     * @var RequestBuilder
50
     */
51
    private $requestBuilder;
52
53
    /**
54
     * @var AbstractTransport
55
     */
56
    private $transport;
57
58
    /**
59
     * @var ResponseParser
60
     */
61
    private $responseParser;
62
63
    /**
64
     * @var IdGeneratorInterface
65
     */
66
    private $generatorId;
67
68
    /**
69
     * @var int
70
     */
71
    private $serverErrorMode;
72
73
    /**
74
     * Client constructor.
75
     *
76
     * @param string $url
77
     * @param int    $serverErrorMode
78
     */
79
    public function __construct(string $url, int $serverErrorMode = self::ERRMODE_SILENT)
80
    {
81
        $this->requestBuilder  = new RequestBuilder();
82
        $this->transport       = new HttpTransport($url);
83
        $this->responseParser  = new ResponseParser();
84
        $this->generatorId     = new IdGenerator();
85
        $this->serverErrorMode = $serverErrorMode;
86
    }
87
88
    /**
89
     * @return RequestBuilder
90
     */
91
    public function getRequestBuilder(): RequestBuilder
92
    {
93
        return $this->requestBuilder;
94
    }
95
96
    /**
97
     * @return AbstractTransport
98
     */
99
    public function getTransport(): AbstractTransport
100
    {
101
        return $this->transport;
102
    }
103
104
    /**
105
     * Set transport engine
106
     *
107
     * @param AbstractTransport $transport
108
     */
109
    public function setTransport(AbstractTransport $transport)
110
    {
111
        $this->transport = $transport;
112
    }
113
114
    /**
115
     * @return ResponseParser
116
     */
117
    public function getResponseParser()
118
    {
119
        return $this->responseParser;
120
    }
121
122
    /**
123
     * @param ResponseParser $responseParser
124
     */
125
    public function setResponseParser($responseParser)
126
    {
127
        $this->responseParser = $responseParser;
128
    }
129
130
    /**
131
     * @return IdGeneratorInterface
132
     */
133
    public function getGeneratorId(): IdGeneratorInterface
134
    {
135
        return $this->generatorId;
136
    }
137
138
    /**
139
     * @param IdGeneratorInterface $idGenerator
140
     */
141
    public function setIdGenerator(IdGeneratorInterface $idGenerator)
142
    {
143
        $this->generatorId = $idGenerator;
144
    }
145
146
    /**
147
     * Make request
148
     *
149
     * @param string $method
150
     * @param array  $parameters
151
     *
152
     * @return $this|mixed
153
     * @throws JsonRpcException
154
     */
155
    public function call(string $method, array $parameters)
156
    {
157
        if ($this->isSingleRequest) {
158
            $this->units = [];
159
        }
160
161
        $this->units[] = new Invoke($this->generatorId->get(), $method, $parameters);
162
163
        if ($this->isSingleRequest) {
164
            $resultSpecifier = $this->execute(new InvokeSpec($this->units, true));
165
166
            if (!$resultSpecifier->isSingleResult()) {
167
                throw new InvalidResponseException();
168
            }
169
170
            list($result) = $resultSpecifier->getResults();
171
172
            if ($result instanceof Result) {
173
                /** @var Result $result */
174
                return $result->getResult();
175
            } elseif ($result instanceof Error) {
176
                /** @var Error $result */
177
                if ($this->serverErrorMode === self::ERRMODE_EXCEPTION) {
178
                    throw $result->getBaseException();
179
                }
180
            }
181
182
            return null;
183
        }
184
185
        return $this;
186
    }
187
188
    /**
189
     * Make notification request
190
     *
191
     * @param string $method
192
     * @param array  $parameters
193
     *
194
     * @return $this|null
195
     */
196
    public function notification(string $method, array $parameters)
197
    {
198
        if ($this->isSingleRequest) {
199
            $this->units = [];
200
        }
201
202
        $this->units[] = new Notification($method, $parameters);
203
204
        if ($this->isSingleRequest) {
205
            $this->execute(new InvokeSpec($this->units, true));
206
            return null;
207
        }
208
209
        return $this;
210
    }
211
212
    /**
213
     * Start batch request
214
     *
215
     * @return $this
216
     */
217
    public function batch()
218
    {
219
        $this->isSingleRequest = false;
220
        $this->units = [];
221
        return $this;
222
    }
223
224
    /**
225
     * Execute batch request
226
     *
227
     * @return array
228
     */
229
    public function batchExecute()
230
    {
231
        $results = $this->execute(new InvokeSpec($this->units, false))->getResults();
232
233
        // Make right order in sequence of results. It's required operation, because JSON-RPC2
234
        // specification define: "The Response objects being returned from a batch call MAY be returned
235
        // in any order within the Array. The Client SHOULD match contexts between the set of Request objects and the
236
        // resulting set of Response objects based on the id member within each Object."
237
238
        $callMap = [];
239
        foreach ($this->units as $index => $unit) {
240
            /** @var Invoke $unit */
241
            $callMap[$unit->getRawId()] = $index;
242
        }
243
244
        if (count($results) !== count($this->units)) {
245
            throw new InvalidResponseException();
246
        }
247
248
        $resultSequence = [];
249
        foreach ($results as $result) {
250
            if ($result instanceof Result) {
251
                /** @var Result $result */
252
                $resultSequence[ $callMap[$result->getId()] ] = $result->getResult();
253
            } elseif ($result instanceof Error) {
254
                /** @var Error $result */
255
                $resultSequence[ $callMap[$result->getId()] ] = $this->serverErrorMode === self::ERRMODE_EXCEPTION ? $result->getBaseException() : null;
256
            }
257
        }
258
        ksort($resultSequence);
259
        $this->units = [];
260
261
        return $resultSequence;
262
    }
263
264
    /**
265
     * @param InvokeSpec $call
266
     *
267
     * @return ResultSpec
268
     */
269
    private function execute(InvokeSpec $call): ResultSpec
270
    {
271
        $request  = $this->requestBuilder->build($call);
272
        $response = $this->transport->request($request);
273
274
        return $this->responseParser->parse($response);
275
    }
276
}
277