Passed
Push — master ( 0cf548...24c0a7 )
by Yo
01:58
created

JsonRpcResponseErrorNormalizer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
nop 3
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
namespace Yoanm\JsonRpcServer\App\Serialization;
3
4
use Yoanm\JsonRpcServer\Domain\Exception\JsonRpcExceptionInterface;
5
6
/**
7
 * JsonRpcResponseErrorNormalizer prepares response data for the "unexpected" errors occur during request processing.
8
 *
9
 * It handles "internal server error" appearance in the response.
10
 * Instance of this class should be attached to {@see \Yoanm\JsonRpcServer\App\Serialization\JsonRpcResponseNormalizer} only in "debug" mode,
11
 * since it will expose vital internal information to the API consumer.
12
 *
13
 * @see \Yoanm\JsonRpcServer\App\Serialization\JsonRpcResponseNormalizer::normalizeError()
14
 */
15
class JsonRpcResponseErrorNormalizer
16
{
17
    /**
18
     * @var int maximum count of trace lines to be displayed.
19
     */
20
    private $maxTraceSize;
21
22
    /**
23
     * @var bool whether to show trace arguments.
24
     */
25
    private $showTraceArguments;
26
27
    /**
28
     * @var bool whether to simplify trace arguments representation.
29
     */
30
    private $simplifyTraceArguments;
31
32
    /**
33
     * @param int $maxTraceSize maximum count of trace lines to be displayed.
34
     * @param bool $showTraceArguments whether to show trace arguments.
35
     * @param bool $simplifyTraceArguments whether to simplify trace arguments representation.
36
     */
37 6
    public function __construct(int $maxTraceSize = 10, bool $showTraceArguments = true, bool $simplifyTraceArguments = true)
38
    {
39 6
        $this->maxTraceSize = $maxTraceSize;
40 6
        $this->showTraceArguments = $showTraceArguments;
41 6
        $this->simplifyTraceArguments = $simplifyTraceArguments;
42
    }
43
44
    /**
45
     * @param JsonRpcExceptionInterface $error
46
     * @return array
47
     */
48 6
    public function normalize(JsonRpcExceptionInterface $error) : array
49
    {
50
        // `JsonRpcExceptionInterface` has a little value regarding debug error data composition on its own,
51
        // thus use previous exception, if available:
52 6
        return $this->composeDebugErrorData($error->getPrevious() ?? $error);
53
    }
54
55
    /**
56
     * @param \Throwable $error
57
     * @return array
58
     */
59 6
    private function composeDebugErrorData(\Throwable $error) : array
60
    {
61 6
        $data = [
62 6
            '_class' => get_class($error),
63 6
            '_code' => $error->getCode(),
64 6
            '_message' => $error->getMessage(),
65 6
        ];
66
67 6
        $trace = $this->filterErrorTrace($error->getTrace());
68 6
        if (!empty($trace)) {
69 4
            $data['_trace'] = $trace;
70
        }
71
72 6
        return $data;
73
    }
74
75
    /**
76
     * @param array $trace raw exception stack trace.
77
     * @return array simplified stack trace.
78
     */
79 6
    private function filterErrorTrace(array $trace): array
80
    {
81 6
        $trace = array_slice($trace, 0, $this->maxTraceSize);
82
83 6
        $result = [];
84 6
        foreach ($trace as $entry) {
85 4
            if (array_key_exists('args', $entry)) {
86 4
                if ($this->showTraceArguments) {
87 3
                    if ($this->simplifyTraceArguments) {
88 3
                        $entry['args'] = $this->simplifyArguments($entry['args']);
89
                    }
90
                } else {
91 1
                    unset($entry['args']);
92
                }
93
            }
94
95 4
            $result[] = $entry;
96
        }
97
98 6
        return $result;
99
    }
100
101
    /**
102
     * Converts arguments array to their simplified representation.
103
     *
104
     * @param array $args arguments array to be converted.
105
     * @return string string representation of the arguments array.
106
     */
107 3
    private function simplifyArguments(array $args) : string
108
    {
109 3
        $count = 0;
110
111 3
        $isAssoc = $args !== array_values($args);
112
113 3
        foreach ($args as $key => $value) {
114 3
            $count++;
115
116 3
            if ($count >= 5) {
117 3
                if ($count > 5) {
118 1
                    unset($args[$key]);
119
                } else {
120 3
                    $args[$key] = '...';
121
                }
122
123 3
                continue;
124
            }
125
126 3
            $args[$key] = $this->simplifyArgument($value);
127
128 3
            if (is_string($key)) {
129 3
                $args[$key] = "'" . $key . "' => " . $args[$key];
130 3
            } elseif ($isAssoc) {
131
                // contains both numeric and string keys:
132 3
                $args[$key] = $key.' => '.$args[$key];
133
            }
134
        }
135
136 3
        return implode(', ', $args);
137
    }
138
139 3
    private function simplifyArgument(mixed $value): mixed
140
    {
141 3
        if (is_object($value)) {
142 3
            return get_class($value);
143 3
        } elseif (is_bool($value)) {
144 3
            return $value ? 'true' : 'false';
145 3
        } elseif (is_string($value)) {
146 3
            if (strlen($value) > 64) {
147 3
                return "'" . substr($value, 0, 64) . "...'";
148
            } else {
149 3
                return "'" . $value . "'";
150
            }
151 3
        } elseif (is_array($value)) {
152 3
            return '[' . $this->simplifyArguments($value) . ']';
153 3
        } elseif ($value === null) {
154 3
            return 'null';
155 3
        } elseif (is_resource($value)) {
156 3
            return 'resource';
157
        }
158
159 3
        return $value;
160
    }
161
}
162