FlattenException   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Test Coverage

Coverage 92.13%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 84
dl 0
loc 284
rs 9.52
c 1
b 0
f 0
ccs 82
cts 89
cp 0.9213
wmc 36

20 Methods

Rating   Name   Duplication   Size   Complexity  
A setLine() 0 3 1
A setPrevious() 0 3 1
A getClassNameFromIncomplete() 0 5 1
A setToString() 0 3 1
A getCode() 0 3 1
A __toString() 0 3 1
A getTrace() 0 3 1
A setClass() 0 3 1
A getPrevious() 0 3 1
A getTraceAsString() 0 8 2
A setTrace() 0 21 4
A __construct() 0 13 2
C flattenArgs() 0 34 12
A getLine() 0 3 1
A getMessage() 0 3 1
A setMessage() 0 3 1
A setCode() 0 3 1
A getFile() 0 3 1
A getClass() 0 3 1
A setFile() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug;
6
7
use __PHP_Incomplete_Class;
8
use ArrayObject;
9
use Exception;
10
use Stringable;
11
use Throwable;
12
13
/**
14
 * FlattenException wraps a PHP Exception to be able to serialize it.
15
 * Implements the Throwable interface
16
 * Basically, this class removes all objects from the trace.
17
 * Ported from Symfony components @link https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Debug/Exception/FlattenException.php
18
 */
19
final class FlattenException implements Stringable
20
{
21
    /**
22
     * @var string
23
     */
24
    private string $message;
25
    /**
26
     * @var int|mixed
27
     */
28
    private mixed $code;
29
    /**
30
     * @var string
31
     */
32
    private string $file;
33
    /**
34
     * @var int
35
     */
36
    private int $line;
37
38
    /**
39
     * @var FlattenException|null
40
     */
41
    private ?FlattenException $previous = null;
42
    /**
43
     * @var array
44
     */
45
    private array $trace;
46
    /**
47
     * @var string
48
     */
49
    private string $toString;
50
    /**
51
     * @var string
52
     */
53
    private string $class;
54
55
    /**
56
     * FlattenException constructor.
57
     */
58
    public function __construct(Throwable $exception)
59 10
    {
60
        $this->setMessage($exception->getMessage());
61 10
        $this->setCode($exception->getCode());
62 10
        $this->setFile($exception->getFile());
63 10
        $this->setLine($exception->getLine());
64 10
        $this->setTrace($exception->getTrace());
65 10
        $this->setToString($exception->__toString());
66 10
        $this->setClass($exception::class);
67 10
68
        $previous = $exception->getPrevious();
69 10
        if ($previous instanceof Exception) {
70 10
            $this->setPrevious(new self($previous));
71 1
        }
72
    }
73
74
    /**
75
     * @param string $string the string representation of the thrown object.
76
     */
77
    private function setToString(string $string): void
78 10
    {
79
        $this->toString = $string;
80 10
    }
81
82
    /**
83
     * Gets the Exception message
84
     *
85
     * @return string the Exception message as a string.
86
     */
87
    public function getMessage(): string
88 1
    {
89
        return $this->message;
90 1
    }
91
92
    /**
93
     * @param string $message the Exception message as a string.
94
     */
95
    private function setMessage(string $message): void
96 10
    {
97
        $this->message = $message;
98 10
    }
99
100
    /**
101
     * Gets the Exception code
102
     *
103
     * @return int|mixed the exception code as integer.
104
     */
105
    public function getCode(): mixed
106 1
    {
107
        return $this->code;
108 1
    }
109
110
    /**
111
     * @param int|mixed $code the exception code as integer.
112
     */
113
    private function setCode(mixed $code): void
114 10
    {
115
        $this->code = $code;
116 10
    }
117
118
    /**
119
     * Gets the file in which the exception occurred
120
     *
121
     * @return string the filename in which the exception was created.
122
     */
123
    public function getFile(): string
124 1
    {
125
        return $this->file;
126 1
    }
127
128
    /**
129
     * @param string $file the filename in which the exception was created.
130
     */
131
    private function setFile(string $file): void
132 10
    {
133
        $this->file = $file;
134 10
    }
135
136
    /**
137
     * Gets the line in which the exception occurred
138
     *
139
     * @return int the line number where the exception was created.
140
     */
141
    public function getLine(): int
142 1
    {
143
        return $this->line;
144 1
    }
145
146
    /**
147
     * @param int $line the line number where the exception was created.
148
     */
149
    private function setLine(int $line): void
150 10
    {
151
        $this->line = $line;
152 10
    }
153
154
    /**
155
     * Gets the stack trace
156
     *
157
     * @return array the Exception stack trace as an array.
158
     */
159
    public function getTrace(): array
160 2
    {
161
        return $this->trace;
162 2
    }
163
164
    /**
165
     * @param array $trace the Exception stack trace as an array.
166
     */
167
    private function setTrace(array $trace): void
168 10
    {
169
        $this->trace = [];
170 10
        foreach ($trace as $entry) {
171 10
            $class = '';
172 10
            $namespace = '';
173 10
            if (isset($entry['class'])) {
174 10
                $parts = explode('\\', $entry['class']);
175 10
                $class = array_pop($parts);
176 10
                $namespace = implode('\\', $parts);
177 10
            }
178
179
            $this->trace[] = [
180 10
                'namespace' => $namespace,
181
                'short_class' => $class,
182
                'class' => $entry['class'] ?? '',
183 10
                'type' => $entry['type'] ?? '',
184 10
                'function' => $entry['function'] ?? null,
185 10
                'file' => $entry['file'] ?? null,
186 10
                'line' => $entry['line'] ?? null,
187 10
                'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
188 10
            ];
189
        }
190
    }
191
192
    /**
193
     * Returns previous Exception
194
     *
195
     * @return FlattenException|null the previous `FlattenException` if available or null otherwise.
196
     */
197
    public function getPrevious(): ?self
198 1
    {
199
        return $this->previous;
200 1
    }
201
202
    /**
203
     * @param FlattenException $previous previous Exception.
204
     */
205
    private function setPrevious(self $previous): void
206 1
    {
207
        $this->previous = $previous;
208 1
    }
209
210
    /**
211
     * Gets the stack trace as a string
212
     *
213
     * @return string the Exception stack trace as a string.
214
     */
215
    public function getTraceAsString(): string
216 1
    {
217
        $remove = "Stack trace:\n";
218 1
        $len = strpos($this->toString, $remove);
219 1
        if ($len === false) {
220 1
            return '';
221
        }
222
        return substr($this->toString, $len + strlen($remove));
223 1
    }
224
225
    /**
226
     * String representation of the exception
227
     *
228
     * @return string the string representation of the exception.
229
     */
230
    public function __toString(): string
231 1
    {
232
        return $this->toString;
233 1
    }
234
235
    /**
236
     * @return string the name of the class in which the exception was created.
237
     */
238
    public function getClass(): string
239 1
    {
240
        return $this->class;
241 1
    }
242
243
    /**
244
     * @param string $class the name of the class in which the exception was created.
245
     */
246
    private function setClass(string $class): void
247 10
    {
248
        $this->class = $class;
249 10
    }
250
251
    /**
252
     * Allows you to sterilize the Exception trace arguments
253
     *
254
     * @param int $level recursion level
255
     * @param int $count number of records counter
256
     *
257
     * @return array arguments tracing.
258
     */
259
    private function flattenArgs(array $args, int $level = 0, int &$count = 0): array
260
    {
261 10
        $result = [];
262
        foreach ($args as $key => $value) {
263 10
            if (++$count > 10000) {
264 10
                return ['array', '*SKIPPED over 10000 entries*'];
265 10
            }
266
            if ($value instanceof __PHP_Incomplete_Class) {
267
                // is_object() returns false on PHP<=7.1
268 10
                $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
269
            } elseif (is_object($value)) {
270
                $result[$key] = ['object', $value::class];
271 10
            } elseif (is_array($value)) {
272 10
                if ($level > 10) {
273 10
                    $result[$key] = ['array', '*DEEP NESTED ARRAY*'];
274 10
                } else {
275
                    $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
276
                }
277 10
            } elseif (null === $value) {
278
                $result[$key] = ['null', null];
279 10
            } elseif (is_bool($value)) {
280 10
                $result[$key] = ['boolean', $value];
281 10
            } elseif (is_int($value)) {
282 10
                $result[$key] = ['integer', $value];
283 10
            } elseif (is_float($value)) {
284 10
                $result[$key] = ['float', $value];
285 10
            } elseif (is_resource($value)) {
286
                $result[$key] = ['resource', get_resource_type($value)];
287 10
            } else {
288
                $result[$key] = ['string', (string)$value];
289
            }
290 10
        }
291
292
        return $result;
293
    }
294 10
295
    /**
296
     * @return string the real class name of an incomplete class
297
     */
298
    private function getClassNameFromIncomplete(__PHP_Incomplete_Class $value): string
299
    {
300
        $array = new ArrayObject($value);
301
302
        return $array['__PHP_Incomplete_Class_Name'];
303
    }
304
}
305