Passed
Push — master ( 8cab53...880433 )
by Alexander
03:10
created

FlattenException::setTrace()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

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