Passed
Push — master ( 025a78...0027ff )
by Sebastian
03:09
created

ConvertHelper_ThrowableInfo::parsePrevious()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AppUtils;
6
7
use Exception;
8
use Throwable;
9
10
class ConvertHelper_ThrowableInfo implements Interface_Optionable
11
{
12
    use Traits_Optionable;
13
    
14
    const ERROR_NO_PREVIOUS_EXCEPTION = 43301;
15
    const ERROR_INVALID_SERIALIZED_DATA_TYPE = 43302;
16
    
17
    const FORMAT_HTML = 'html';
18
19
    const CONTEXT_COMMAND_LINE = 'cli';
20
    const CONTEXT_WEB = 'web';
21
22
    /**
23
    * @var Throwable
24
    */
25
    protected $exception;
26
    
27
   /**
28
    * @var ConvertHelper_ThrowableInfo_Call[]
29
    */
30
    protected $calls = array();
31
    
32
   /**
33
    * @var integer
34
    */
35
    protected $code = 0;
36
    
37
   /**
38
    * @var string
39
    */
40
    protected $message;
41
    
42
   /**
43
    * @var integer
44
    */
45
    protected $callsCount = 0;
46
    
47
   /**
48
    * @var ConvertHelper_ThrowableInfo
49
    */
50
    protected $previous;
51
    
52
   /**
53
    * @var string
54
    */
55
    protected $referer = '';
56
    
57
   /**
58
    * @var Microtime
59
    */
60
    protected $date;
61
    
62
   /**
63
    * @var string
64
    */
65
    protected $context = self::CONTEXT_WEB;
66
67
    /**
68
     * @var string
69
     */
70
    private $class = '';
71
72
    /**
73
     * @var string
74
     */
75
    private $details = '';
76
77
    /**
78
     * @param array<string,mixed>|Throwable $subject
79
     * @throws Exception
80
     */
81
    protected function __construct($subject)
82
    {
83
        if(is_array($subject))
84
        {
85
            $this->parseSerialized($subject);
86
        }
87
        else
88
        {
89
            $this->parseException($subject);
90
        }
91
    }
92
    
93
    public static function fromThrowable(Throwable $e) : ConvertHelper_ThrowableInfo
94
    {
95
        return new ConvertHelper_ThrowableInfo($e);
96
    }
97
98
    /**
99
     * @param array<string,mixed> $serialized
100
     * @return ConvertHelper_ThrowableInfo
101
     * @throws ConvertHelper_Exception
102
     */
103
    public static function fromSerialized(array $serialized) : ConvertHelper_ThrowableInfo
104
    {
105
        return new ConvertHelper_ThrowableInfo(ConvertHelper_ThrowableInfo_Serializer::unserialize($serialized));
106
    }
107
    
108
    public function getCode() : int
109
    {
110
        return $this->code;
111
    }
112
    
113
    public function getMessage() : string
114
    {
115
        return $this->message;
116
    }
117
118
    public function getDefaultOptions() : array
119
    {
120
        return array(
121
            'folder-depth' => 2
122
        );
123
    }
124
    
125
    public function hasPrevious() : bool
126
    {
127
        return isset($this->previous);
128
    }
129
    
130
   /**
131
    * Retrieves the information on the previous exception.
132
    * 
133
    * NOTE: Throws an exception if there is no previous 
134
    * exception. Use hasPrevious() first to avoid this.
135
    * 
136
    * @throws ConvertHelper_Exception
137
    * @return ConvertHelper_ThrowableInfo
138
    * @see ConvertHelper_ThrowableInfo::ERROR_NO_PREVIOUS_EXCEPTION
139
    */
140
    public function getPrevious() : ConvertHelper_ThrowableInfo
141
    {
142
        if(isset($this->previous)) {
143
            return $this->previous;
144
        }
145
        
146
        throw new ConvertHelper_Exception(
147
            'Cannot get previous exception info: none available.',
148
            'Always use hasPrevious() before using getPrevious() to avoid this error.',
149
            self::ERROR_NO_PREVIOUS_EXCEPTION
150
        );
151
    }
152
    
153
    public function hasCode() : bool
154
    {
155
        return !empty($this->code);
156
    }
157
    
158
   /**
159
    * Improved text-only exception trace.
160
    */
161
    public function toString() : string
162
    {
163
        return (new ConvertHelper_ThrowableInfo_StringConverter($this))
164
            ->toString();
165
    }
166
    
167
   /**
168
    * Retrieves the URL of the page in which the exception
169
    * was thrown, if applicable: in CLI context, this will
170
    * return an empty string.
171
    * 
172
    * @return string
173
    */
174
    public function getReferer() : string
175
    {
176
        return $this->referer;
177
    }
178
    
179
   /**
180
    * Whether the exception occurred in a command line context.
181
    * @return bool
182
    */
183
    public function isCommandLine() : bool
184
    {
185
        return $this->getContext() === self::CONTEXT_COMMAND_LINE;
186
    }
187
    
188
   /**
189
    * Whether the exception occurred during an http request.
190
    * @return bool
191
    */
192
    public function isWebRequest() : bool
193
    {
194
        return $this->getContext() === self::CONTEXT_WEB;
195
    }
196
    
197
   /**
198
    * Retrieves the context identifier, i.e. if the exception
199
    * occurred in a command line context or regular web request.
200
    * 
201
    * @return string
202
    * 
203
    * @see ConvertHelper_ThrowableInfo::isCommandLine()
204
    * @see ConvertHelper_ThrowableInfo::isWebRequest()
205
    * @see ConvertHelper_ThrowableInfo::CONTEXT_COMMAND_LINE
206
    * @see ConvertHelper_ThrowableInfo::CONTEXT_WEB
207
    */
208
    public function getContext() : string
209
    {
210
        return $this->context;
211
    }
212
    
213
   /**
214
    * Retrieves the date of the exception, and approximate time:
215
    * since exceptions do not store time, this is captured the 
216
    * moment the ThrowableInfo is created.
217
    * 
218
    * @return Microtime
219
    */
220
    public function getDate() : Microtime
221
    {
222
        return $this->date;
223
    }
224
225
    /**
226
     * Serializes all information on the exception to an
227
     * associative array. This can be saved (file, database,
228
     * session...), and later be restored into a throwable
229
     * info instance using the fromSerialized() method.
230
     *
231
     * @return array<string,mixed>
232
     * @throws ConvertHelper_Exception
233
     * @see ConvertHelper_ThrowableInfo::fromSerialized()
234
     */
235
    public function serialize() : array
236
    {
237
        return ConvertHelper_ThrowableInfo_Serializer::serialize($this);
238
    }
239
240
   /**
241
    * Sets the maximum folder depth to show in the 
242
    * file paths, to avoid them being too long.
243
    * 
244
    * @param int $depth
245
    * @return ConvertHelper_ThrowableInfo
246
    */
247
    public function setFolderDepth(int $depth) : ConvertHelper_ThrowableInfo
248
    {
249
        return $this->setOption('folder-depth', $depth);
250
    }
251
    
252
   /**
253
    * Retrieves the current folder depth option value.
254
    * 
255
    * @return int
256
    * @see ConvertHelper_ThrowableInfo::setFolderDepth()
257
    */
258
    public function getFolderDepth() : int
259
    {
260
        $depth = $this->getOption('folder-depth');
261
        if(!empty($depth)) {
262
            return $depth;
263
        }
264
        
265
        return 2;
266
    }
267
    
268
   /**
269
    * Retrieves all function calls that led to the error,
270
    * ordered from latest to earliest (the first one in
271
    * the stack is actually the last call).
272
    *
273
    * @return ConvertHelper_ThrowableInfo_Call[]
274
    */
275
    public function getCalls()
276
    {
277
        return $this->calls;
278
    }
279
280
    /**
281
     * Retrieves the last call that led to the error.
282
     *
283
     * @return ConvertHelper_ThrowableInfo_Call
284
     */
285
    public function getFinalCall() : ConvertHelper_ThrowableInfo_Call
286
    {
287
        return $this->calls[0];
288
    }
289
    
290
   /**
291
    * Returns the amount of function and method calls in the stack trace.
292
    * @return int
293
    */
294
    public function countCalls() : int
295
    {
296
        return $this->callsCount;
297
    }
298
299
300
    /**
301
     * @param array<string,mixed> $serialized
302
     * @throws Exception
303
     */
304
    private function parseSerialized(array $serialized) : void
305
    {
306
        $this->date = new Microtime($serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_DATE]);
307
        $this->class = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_CLASS];
308
        $this->details = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_DETAILS];
309
        $this->code = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_CODE];
310
        $this->message = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_MESSAGE];
311
        $this->referer = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_REFERER];
312
        $this->context = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_CONTEXT];
313
        $this->callsCount = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_AMOUNT_CALLS];
314
        $this->previous = $serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_PREVIOUS];
315
316
        $this->setOptions($serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_OPTIONS]);
317
        
318
        foreach($serialized[ConvertHelper_ThrowableInfo_Serializer::SERIALIZED_CALLS] as $def)
319
        {
320
            $this->calls[] = ConvertHelper_ThrowableInfo_Call::fromSerialized($this, $def);
321
        }
322
    }
323
    
324
    protected function parseException(Throwable $e) : void
325
    {
326
        $this->date = new Microtime();
327
        $this->class = get_class($e);
328
329
        if($e instanceof BaseException)
330
        {
331
            $this->details = $e->getDetails();
332
        }
333
334
        $this->parseMessage($e);
335
        $this->parsePrevious($e);
336
        $this->parseContext();
337
        $this->parseTrace($e);
338
    }
339
340
    /**
341
     * Retrieves the class name of the exception.
342
     *
343
     * @return string
344
     */
345
    public function getClass() : string
346
    {
347
        return $this->class;
348
    }
349
350
    /**
351
     * Converts the exception's information into a human-
352
     * readable string containing the exception's essentials.
353
     *
354
     * It includes any previous exceptions as well, recursively.
355
     *
356
     * @param bool $withDeveloperInfo Whether to include developer-specific info
357
     *                                when available (which may include sensitive
358
     *                                information).
359
     * @return string
360
     * @throws ConvertHelper_Exception
361
     */
362
    public function renderErrorMessage(bool $withDeveloperInfo=false) : string
363
    {
364
        return (new ConvertHelper_ThrowableInfo_MessageRenderer($this, $withDeveloperInfo))
365
            ->render();
366
    }
367
368
    public function getDetails() : string
369
    {
370
        return $this->details;
371
    }
372
373
    public function hasDetails() : bool
374
    {
375
        return !empty($this->details);
376
    }
377
    
378
    public function __toString()
379
    {
380
        return $this->toString();
381
    }
382
383
    /**
384
     * @param Throwable $e
385
     */
386
    private function parseTrace(Throwable $e) : void
387
    {
388
        $trace = $e->getTrace();
389
390
        // add the origin file as entry
391
        array_unshift($trace, array(
392
            'file' => $e->getFile(),
393
            'line' => $e->getLine()
394
        ));
395
396
        $idx = 1;
397
398
        foreach ($trace as $entry)
399
        {
400
            $this->calls[] = ConvertHelper_ThrowableInfo_Call::fromTrace($this, $idx, $entry);
401
402
            $idx++;
403
        }
404
405
        // we want the last function call first
406
        $this->calls = array_reverse($this->calls, false);
407
408
        $this->callsCount = count($this->calls);
409
    }
410
411
    private function parseContext() : void
412
    {
413
        if (!isset($_REQUEST['REQUEST_URI']))
414
        {
415
            $this->context = self::CONTEXT_COMMAND_LINE;
416
        }
417
418
        if (isset($_SERVER['REQUEST_URI']))
419
        {
420
            $this->referer = $_SERVER['REQUEST_URI'];
421
        }
422
    }
423
424
    /**
425
     * @param Throwable $e
426
     */
427
    private function parseMessage(Throwable $e) : void
428
    {
429
        $code = $e->getCode();
430
        $this->message = $e->getMessage();
431
432
        if (is_integer($code))
0 ignored issues
show
introduced by
The condition is_integer($code) is always true.
Loading history...
433
        {
434
            $this->code = $code;
435
        }
436
        else
437
        {
438
            $this->message = 'Original error code: [' . $code . ']. ' . $this->message;
439
        }
440
    }
441
442
    /**
443
     * @param Throwable $e
444
     */
445
    protected function parsePrevious(Throwable $e) : void
446
    {
447
        $previous = $e->getPrevious();
448
449
        if (!empty($previous))
450
        {
451
            $this->previous = ConvertHelper::throwable2info($previous);
452
        }
453
    }
454
}
455