simplifyArguments()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

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