Passed
Push — master ( a4ffc4...615af5 )
by Alexander
03:23
created

FlattenException   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Test Coverage

Coverage 90.32%

Importance

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