RpcHandler::oneRun()   B
last analyzed

Complexity

Conditions 8
Paths 39

Size

Total Lines 76
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 9.4049

Importance

Changes 4
Bugs 2 Features 1
Metric Value
cc 8
eloc 46
c 4
b 2
f 1
nc 39
nop 2
dl 0
loc 76
ccs 36
cts 50
cp 0.72
crap 9.4049
rs 7.9337

How to fix   Long Method   

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
declare(strict_types=1);
4
5
namespace Onnov\JsonRpcServer;
6
7
use JsonException;
8
use JsonMapper;
9
use Onnov\JsonRpcServer\Definition\RpcAuthDefinition;
10
use Onnov\JsonRpcServer\Error\RpcError;
11
use Onnov\JsonRpcServer\Exception\InternalErrorException;
12
use Onnov\JsonRpcServer\Model\RpcRun;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
use stdClass;
16
use Throwable;
17
use Onnov\JsonRpcServer\Exception\InvalidAuthorizeException;
18
use Onnov\JsonRpcServer\Exception\InvalidParamsException;
19
use Onnov\JsonRpcServer\Exception\InvalidRequestException;
20
use Onnov\JsonRpcServer\Exception\MethodErrorException;
21
use Onnov\JsonRpcServer\Exception\MethodNotFoundException;
22
use Onnov\JsonRpcServer\Exception\ParseErrorException;
23
use Onnov\JsonRpcServer\Service\ApiExecService;
24
use Onnov\JsonRpcServer\Service\RpcService;
25
use Onnov\JsonRpcServer\Validator\JsonRpcSchema;
26
use Onnov\JsonRpcServer\Validator\JsonSchemaValidator;
27
28
/**
29
 * Class RpcHandler
30
 *
31
 * @package Onnov\JsonRpcServer
32
 */
33
class RpcHandler
34
{
35
    private ?Throwable $throwable = null;
36
37
    private ?LoggerInterface $logger;
38
    private RpcService $rpcService;
39
    private ApiExecService $apiExecService;
40
    private RpcError $rpcError;
41
    private bool $detailError = false;
42
43
    /**
44
     * JsonRpcHandler constructor.
45
     *
46
     * @param LoggerInterface|null $logger
47
     */
48 9
    public function __construct(LoggerInterface $logger = null)
49
    {
50 9
        $this->logger = $logger;
51
52 9
        $validator = new JsonSchemaValidator();
53 9
        $rpcSchema = new JsonRpcSchema();
54 9
        $mapper = new JsonMapper();
55
56 9
        $this->rpcService = new RpcService($validator, $rpcSchema, $mapper);
57 9
        $this->apiExecService = new ApiExecService($validator, $rpcSchema, $mapper);
58
    }
59
60
    /**
61
     * @param  RpcRun $rpcRun
62
     * @return string
63
     * @throws JsonException
64
     */
65 9
    public function run(RpcRun $rpcRun): string
66
    {
67 9
        if (isset($this->rpcError) === false) {
68 9
            $this->setRpcError(new RpcError($rpcRun->getAuth()->getAuthError()));
69
        }
70 9
        $rpcService = $this->getRpcService();
71
72
        /**
73
         * Парсим
74
         */
75
        try {
76 9
            $data = $rpcService->jsonParse($rpcRun->getJson());
77
78 8
            $resArr = [];
79 8
            foreach ($data as $rpc) {
80
                switch (true) {
81 8
                case ($rpc instanceof stdClass):
82
                    // TODO впилить паралельное выполнение, возможно amphp/amp
83 8
                    $resArr[] = $this->oneRun(
84 8
                        $rpcRun,
85 8
                        $rpc
86 8
                    );
87 8
                    break;
88
                default:
89 1
                    $resArr[] = $this->getJsonStrError();
90
                }
91
            }
92
93 8
            $res = implode(',', $resArr);
94
95 8
            if ($rpcService->isBatch()) {
96 8
                $res = '[' . $res . ']';
97
            }
98 1
        } catch (ParseErrorException $e) {
99 1
            $res = $this->getJsonStrError($e->getPrevious());
100
        }
101
102 9
        return $res;
103
    }
104
105
    /**
106
     * @param  RpcRun   $rpcRun
107
     * @param  stdClass $rpc
108
     * @return string
109
     * @throws JsonException
110
     */
111 8
    private function oneRun(
112
        RpcRun $rpcRun,
113
        stdClass $rpc
114
    ): string {
115 8
        $res = [
116 8
            'jsonrpc' => '2.0',
117 8
        ];
118
119 8
        $error = null;
120
121
        try {
122
            /**
123
             * валидируем и парсим JsonRPC
124
             */
125 8
            $rpcObj = $this->getRpcService()->getRpc($rpc);
126
127
            /**
128
             * Проверим авторизацию
129
             */
130 7
            $this->authCheck($rpcObj->getMethod(), $rpcRun->getAuth());
131
132
            /**
133
             * Пытаемся выполнить запрос
134
             */
135 6
            $res['result'] = $this->getApiExeService()->exe(
136 6
                $rpcRun,
137 6
                $rpcObj
138 6
            );
139 4
        } catch (InvalidAuthorizeException | MethodNotFoundException $e) {
140 2
            $this->setThrowable($e);
141 2
            $error = $this
142 2
                ->getRpcError()
143 2
                ->getErrorByName($this->getExceptionName($e), $e);
144
        } catch (
145 2
            InternalErrorException
146 2
            | InvalidParamsException
147 2
            | InvalidRequestException
148 2
            | MethodErrorException
149 2
            | ParseErrorException $e
150
        ) {
151 2
            $this->setThrowable($e);
152 2
            $error = $this
153 2
                ->getRpcError()
154 2
                ->getErrorByName($this->getExceptionName($e), $e);
155
156 2
            if ($e->getData() !== null) {
157 2
                $error->setData((object)$e->getData());
158
            }
159
        } catch (Throwable $t) {
160
            $this->setThrowable($t);
161
            $error = $this
162
                ->getRpcError()
163
                ->getErrorByName('Throwable');
164
            $error->setData(
165
                (object)[
166
                    'exception' => get_class($t),
167
                    'code' => $t->getCode(),
168
                    'message' => $t->getMessage(),
169
                    'file' => $t->getFile(),
170
                    'line' => $t->getLine(),
171
                ]
172
            );
173
        }
174
175 8
        if ($error !== null) {
176 4
            $res['error'] = (object)[
177 4
                'code' => $error->getCode(),
178 4
                'message' => $error->getMessage(),
179 4
            ];
180 4
            if ($this->isDetailError() && $error->getData() !== null) {
181 2
                $res['error']->data = $error->getData();
182
            }
183 4
            $this->log($error->getLogLevel(), $res);
184
        }
185
186 8
        return $this->getJsonResult($rpc, $res);
187
    }
188
189
    /**
190
     * @param  Throwable $throw
191
     * @return string
192
     */
193 4
    private function getExceptionName(Throwable $throw): string
194
    {
195 4
        $exception = get_class($throw);
196
197 4
        return substr($exception, (int)strrpos($exception, '\\') + 1);
198
    }
199
200
    /**
201
     * @param string            $method
202
     * @param RpcAuthDefinition $auth
203
     */
204 7
    private function authCheck(string $method, RpcAuthDefinition $auth): void
205
    {
206 7
        if ($auth->isResultAuth() === false
207 7
            && in_array($method, $auth->getProcWithoutAuth(), true) === false
208
        ) {
209 1
            throw new InvalidAuthorizeException();
210
        }
211
    }
212
213
    /**
214
     * @param  stdClass $rpc
215
     * @param  mixed[]  $res
216
     * @return string
217
     * @throws JsonException
218
     */
219 8
    private function getJsonResult(stdClass $rpc, array &$res): string
220
    {
221 8
        if (isset($rpc->id)) {
222 8
            $res['id'] = $rpc->id;
223
        }
224
225
        try {
226 8
            $result = json_encode($res, JSON_THROW_ON_ERROR);
227
        } catch (JsonException $e) {
228
            $result = $this->getJsonStrError($e, $rpc->id ?? 'error');
229
        }
230
231 8
        return $result;
232
    }
233
234
    /**
235
     * @param  Throwable|null $err
236
     * @param  mixed          $id
237
     * @return string
238
     * @throws JsonException
239
     */
240 2
    private function getJsonStrError(Throwable $err = null, $id = 'error'): string
241
    {
242 2
        if (gettype($id) === 'string') {
243 2
            $id = '"' . $id . '"';
244
        }
245
246 2
        $data = '';
247 2
        $msg = 'Parse error';
248 2
        if ($err !== null) {
249 1
            $data = ', "data": {"code": ' . $err->getCode() . ', "message": "' . $err->getMessage() . '"}';
250 1
            $msg = 'Parse error (' . $err->getMessage() . ')';
251
        }
252
253 2
        $res = [
254 2
            '{"jsonrpc": "2.0",',
255 2
            '"error":',
256 2
            '{"code": -32700, "message": "Parse error"' . $data . '},',
257 2
            '"id": ' . $id . '}',
258 2
        ];
259
260 2
        $this->log(LogLevel::ERROR, ['error' => (object)['message' => $msg]]);
261
262 2
        return implode(' ', $res);
263
    }
264
265
    /**
266
     * @param  string|null $errorLevel
267
     * @param  mixed[]     $res
268
     * @throws JsonException
269
     */
270 6
    private function log(?string $errorLevel, array $res): void
271
    {
272 6
        $logger = $this->getLogger();
273 6
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
274
            $error = json_decode(
275
                json_encode($res['error'] ?? [], JSON_THROW_ON_ERROR),
276
                true
277
            );
278
            $logger->log($errorLevel, $error->message ?? 'no message', $error);
279
        }
280
    }
281
282
    /**
283
     * @return LoggerInterface|null
284
     */
285 6
    public function getLogger(): ?LoggerInterface
286
    {
287 6
        return $this->logger;
288
    }
289
290
    /**
291
     * @param LoggerInterface|null $logger
292
     */
293
    public function setLogger(?LoggerInterface $logger): void
294
    {
295
        $this->logger = $logger;
296
    }
297
298
    /**
299
     * @return RpcService
300
     */
301 9
    public function getRpcService(): RpcService
302
    {
303 9
        return $this->rpcService;
304
    }
305
306
    /**
307
     * @return ApiExecService
308
     */
309 6
    public function getApiExeService(): ApiExecService
310
    {
311 6
        return $this->apiExecService;
312
    }
313
314
    /**
315
     * @return RpcError
316
     */
317 4
    public function getRpcError(): RpcError
318
    {
319 4
        return $this->rpcError;
320
    }
321
322
    /**
323
     * @param RpcError $rpcError
324
     */
325 9
    public function setRpcError(RpcError $rpcError): void
326
    {
327 9
        $this->rpcError = $rpcError;
328
    }
329
330
    /**
331
     * @return Throwable|null
332
     */
333
    public function getThrowable(): ?Throwable
334
    {
335
        return $this->throwable;
336
    }
337
338
    /**
339
     * @param  Throwable|null $throwable
340
     * @return RpcHandler
341
     */
342 4
    public function setThrowable(?Throwable $throwable): RpcHandler
343
    {
344 4
        $this->throwable = $throwable;
345 4
        return $this;
346
    }
347
348
    /**
349
     * @return ApiExecService
350
     */
351
    public function getApiExecService(): ApiExecService
352
    {
353
        return $this->apiExecService;
354
    }
355
356
    /**
357
     * @param  ApiExecService $apiExecService
358
     * @return RpcHandler
359
     */
360
    public function setApiExecService(ApiExecService $apiExecService): RpcHandler
361
    {
362
        $this->apiExecService = $apiExecService;
363
        return $this;
364
    }
365
366
    /**
367
     * @return bool
368
     */
369 4
    public function isDetailError(): bool
370
    {
371 4
        return $this->detailError;
372
    }
373
374
    /**
375
     * @param  bool $detailError
376
     * @return RpcHandler
377
     */
378 2
    public function setDetailError(bool $detailError): RpcHandler
379
    {
380 2
        $this->detailError = $detailError;
381 2
        return $this;
382
    }
383
}
384