Completed
Push — master ( 39b3fc...3ebb26 )
by Sergey
12:04 queued 07:06
created

RpcHandler::oneRun()   B

Complexity

Conditions 7
Paths 39

Size

Total Lines 65
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 8.0894

Importance

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

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
 * @package Onnov\JsonRpcServer
31
 */
32
class RpcHandler
33
{
34
    /** @var LoggerInterface|null */
35
    private $logger;
36
37
    /** @var RpcService */
38
    private $rpcService;
39
40
    /** @var ApiExecService */
41
    private $apiExecService;
42
43
    /** @var RpcError */
44
    private $rpcError;
45
46
    /**
47
     * JsonRpcHandler constructor.
48
     * @param LoggerInterface|null $logger
49
     */
50 9
    public function __construct(LoggerInterface $logger = null)
51
    {
52 9
        $this->logger = $logger;
53
54 9
        $validator = new JsonSchemaValidator();
55 9
        $rpcSchema = new JsonRpcSchema();
56 9
        $mapper = new JsonMapper();
57
58 9
        $this->rpcService = new RpcService($validator, $rpcSchema, $mapper);
59 9
        $this->apiExecService = new ApiExecService($validator, $rpcSchema, $mapper);
60 9
    }
61
62
    /**
63
     * @param RpcRun $rpcRun
64
     * @return string
65
     * @throws JsonException
66
     */
67 9
    public function run(RpcRun $rpcRun): string
68
    {
69 9
        if (isset($this->rpcError) === false) {
70 9
            $this->setRpcError(new RpcError($rpcRun->getAuth()->getAuthError()));
71
        }
72 9
        $rpcService = $this->getRpcService();
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
                            $rpc
86
                        );
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
        $res = [
116 8
            'jsonrpc' => '2.0',
117
        ];
118
119 8
        $error = null;
120
121
        try {
122
            /** валидируем и парсим JsonRPC */
123 8
            $rpcObj = $this->getRpcService()->getRpc($rpc);
124
125
            /** Проверим авторизацию */
126 7
            $this->authCheck($rpcObj->getMethod(), $rpcRun->getAuth());
127
128
            /** Пытаемся выполнить запрос */
129 6
            $res['result'] = $this->getApiExeService()->exe(
130 6
                $rpcRun,
131
                $rpcObj
132
            );
133 4
        } catch (InvalidAuthorizeException | MethodNotFoundException $e) {
134
            $error = $this
135 2
                ->getRpcError()
136 2
                ->getErrorByName($this->getExceptionName($e), $e);
137
        } catch (
138 2
            InternalErrorException
139
            | InvalidParamsException
140
            | InvalidRequestException
141
            | MethodErrorException
142
            | ParseErrorException $e
143
        ) {
144
            $error = $this
145 2
                ->getRpcError()
146 2
                ->getErrorByName($this->getExceptionName($e), $e);
147
148 2
            if ($e->getData() !== null) {
149 2
                $error->setData((object)$e->getData());
150
            }
151
        } catch (Throwable $t) {
152
            $error = $this
153
                ->getRpcError()
154
                ->getErrorByName('Throwable');
155
            $error->setData((object)[
156
                'exception' => get_class($t),
157
                'code'      => $t->getCode(),
158
                'message'   => $t->getMessage(),
159
                'file'      => $t->getFile(),
160
                'line'      => $t->getLine(),
161
            ]);
162
        }
163
164 8
        if ($error !== null) {
165 4
            $res['error'] = (object)[
166 4
                'code' => $error->getCode(),
167 4
                'message' => $error->getMessage(),
168
            ];
169 4
            if ($error->getData() !== null) {
170 2
                $res['error']->data = $error->getData();
171
            }
172 4
            $this->log($error->getLogLevel(), $res);
173
        }
174
175 8
        return $this->getJsonResult($rpc, $res);
176
    }
177
178
    /**
179
     * @param Throwable $throw
180
     * @return string
181
     */
182 4
    private function getExceptionName(Throwable $throw): string
183
    {
184 4
        $exception = get_class($throw);
185
186 4
        return substr($exception, (int)strrpos($exception, '\\') + 1);
187
    }
188
189
    /**
190
     * @param string $method
191
     * @param RpcAuthDefinition $auth
192
     */
193 7
    private function authCheck(string $method, RpcAuthDefinition $auth): void
194
    {
195
        if (
196 7
            $auth->isResultAuth() === false
197 7
            && in_array($method, $auth->getProcWithoutAuth(), true) === false
198
        ) {
199 1
            throw new InvalidAuthorizeException();
200
        }
201 6
    }
202
203
    /**
204
     * @param stdClass $rpc
205
     * @param mixed[] $res
206
     * @return string
207
     * @throws JsonException
208
     */
209 8
    private function getJsonResult(stdClass $rpc, array &$res): string
210
    {
211 8
        if (isset($rpc->id)) {
212 8
            $res['id'] = $rpc->id;
213
        }
214
215
        try {
216 8
            $result = json_encode($res, JSON_THROW_ON_ERROR);
217
        } catch (JsonException $e) {
218
            $result = $this->getJsonStrError($e, $rpc->id ?? 'error');
219
        }
220
221 8
        return $result;
222
    }
223
224
    /**
225
     * @param Throwable|null $err
226
     * @param mixed $id
227
     * @return string
228
     * @throws JsonException
229
     */
230 2
    private function getJsonStrError(Throwable $err = null, $id = 'error'): string
231
    {
232 2
        if (gettype($id) === 'string') {
233 2
            $id = '"' . $id . '"';
234
        }
235
236 2
        $data = '';
237 2
        $msg = 'Parse error';
238 2
        if ($err !== null) {
239 1
            $data = ', "data" => {"code": ' . $err->getCode() . ', "message": "' . $err->getMessage() . '"}';
240 1
            $msg = 'Parse error (' . $err->getMessage() . ')';
241
        }
242
243
        $res = [
244 2
            '{"jsonrpc": "2.0",',
245 2
            '"error":',
246 2
            '{"code": -32700, "message": "Parse error"' . $data . '},',
247 2
            '"id": ' . $id . '}',
248
        ];
249
250 2
        $this->log(LogLevel::ERROR, ['error' => (object)['message' => $msg]]);
251
252 2
        return implode(' ', $res);
253
    }
254
255
    /**
256
     * @param string|null $errorLevel
257
     * @param mixed[] $res
258
     * @throws JsonException
259
     */
260 6
    private function log(?string $errorLevel, array $res): void
261
    {
262 6
        $logger = $this->getLogger();
263 6
        if ($errorLevel !== null && $logger instanceof LoggerInterface) {
264
            $error = json_decode(
265
                json_encode($res['error'] ?? [], JSON_THROW_ON_ERROR),
266
                true
267
            );
268
            $logger->log($errorLevel, $error->message ?? 'no message', $error);
269
        }
270 6
    }
271
272
    /**
273
     * @return LoggerInterface|null
274
     */
275 6
    public function getLogger(): ?LoggerInterface
276
    {
277 6
        return $this->logger;
278
    }
279
280
    /**
281
     * @param LoggerInterface|null $logger
282
     */
283
    public function setLogger(?LoggerInterface $logger): void
284
    {
285
        $this->logger = $logger;
286
    }
287
288
    /**
289
     * @return RpcService
290
     */
291 9
    public function getRpcService(): RpcService
292
    {
293 9
        return $this->rpcService;
294
    }
295
296
    /**
297
     * @return ApiExecService
298
     */
299 6
    public function getApiExeService(): ApiExecService
300
    {
301 6
        return $this->apiExecService;
302
    }
303
304
    /**
305
     * @return RpcError
306
     */
307 4
    public function getRpcError(): RpcError
308
    {
309 4
        return $this->rpcError;
310
    }
311
312
    /**
313
     * @param RpcError $rpcError
314
     */
315 9
    public function setRpcError(RpcError $rpcError): void
316
    {
317 9
        $this->rpcError = $rpcError;
318 9
    }
319
}
320