Test Failed
Push — master ( 142697...051a0a )
by Sergey
08:41
created

RpcHandler::oneRun()   B

Complexity

Conditions 7
Paths 39

Size

Total Lines 68
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 8.0894

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 7
eloc 45
c 3
b 1
f 1
nc 39
nop 2
dl 0
loc 68
ccs 23
cts 32
cp 0.7188
crap 8.0894
rs 8.2666

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