Passed
Push — master ( b33de3...cb7df5 )
by Sebastian
02:24
created

ConvertHelper_ThrowableInfo::hasCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AppUtils;
6
7
class ConvertHelper_ThrowableInfo implements Interface_Optionable
8
{
9
    use Traits_Optionable;
10
    
11
    const ERROR_NO_PREVIOUS_EXCEPTION = 43301;
12
    
13
    const FORMAT_HTML = 'html';
14
    const CONTEXT_COMMAND_LINE = 'cli';
15
    const CONTEXT_WEB = 'web';
16
    
17
   /**
18
    * @var \Throwable
19
    */
20
    protected $exception;
21
    
22
   /**
23
    * @var ConvertHelper_ThrowableInfo_Call[]
24
    */
25
    protected $calls = array();
26
    
27
   /**
28
    * @var integer
29
    */
30
    protected $code;
31
    
32
   /**
33
    * @var string
34
    */
35
    protected $message;
36
    
37
   /**
38
    * @var integer
39
    */
40
    protected $callsCount = 0;
41
    
42
   /**
43
    * @var ConvertHelper_ThrowableInfo
44
    */
45
    protected $previous;
46
    
47
   /**
48
    * @var string
49
    */
50
    protected $referer = '';
51
    
52
   /**
53
    * @var \DateTime
54
    */
55
    protected $date;
56
    
57
   /**
58
    * @var string
59
    */
60
    protected $context = self::CONTEXT_WEB;
61
    
62
    protected function __construct($subject)
63
    {
64
        if(is_array($subject))
65
        {
66
            $this->parseSerialized($subject);
67
        }
68
        else
69
        {
70
            $this->parseException($subject);
71
        }
72
    }
73
    
74
    public static function fromThrowable(\Throwable $e)
75
    {
76
        return new ConvertHelper_ThrowableInfo($e);
77
    }
78
    
79
    public static function fromSerialized(array $serialized)
80
    {
81
        return new ConvertHelper_ThrowableInfo($serialized);
82
    }
83
    
84
    public function getCode() : int
85
    {
86
        return $this->code;
87
    }
88
    
89
    public function getMessage() : string
90
    {
91
        return $this->message;
92
    }
93
94
    public function getDefaultOptions() : array
95
    {
96
        return array(
97
            'folder-depth' => 2
98
        );
99
    }
100
    
101
    public function hasPrevious() : bool
102
    {
103
        return isset($this->previous);
104
    }
105
    
106
   /**
107
    * Retrieves the information on the previous exception.
108
    * 
109
    * NOTE: Throws an exception if there is no previous 
110
    * exception. Use hasPrevious() first to avoid this.
111
    * 
112
    * @throws ConvertHelper_Exception
113
    * @return ConvertHelper_ThrowableInfo
114
    * @see ConvertHelper_ThrowableInfo::ERROR_NO_PREVIOUS_EXCEPTION
115
    */
116
    public function getPrevious() : ConvertHelper_ThrowableInfo
117
    {
118
        if(isset($this->previous)) {
119
            return $this->previous;
120
        }
121
        
122
        throw new ConvertHelper_Exception(
123
            'Cannot get previous exception info: none available.',
124
            'Always use hasPrevious() before using getPrevious() to avoid this error.',
125
            self::ERROR_NO_PREVIOUS_EXCEPTION
126
        );
127
    }
128
    
129
    public function hasCode() : bool
130
    {
131
        return !empty($this->code);
132
    }
133
    
134
   /**
135
    * Improved textonly exception trace.
136
    */
137
    public function toString() : string
138
    {
139
        $calls = $this->getCalls();
140
        
141
        $string = 'Exception';
142
        
143
        if($this->hasCode()) {
144
            $string .= ' #'.$this->code;
145
        }
146
        
147
        $string .= ': '.$this->getMessage().PHP_EOL;
148
        
149
        foreach($calls as $call) 
150
        {
151
            $string .= $call->toString().PHP_EOL;
152
        }
153
        
154
        if($this->hasPrevious())
155
        {
156
            $string .= PHP_EOL.PHP_EOL.
157
            'Previous error:'.PHP_EOL.PHP_EOL.
158
            $this->previous->toString();
159
        }
160
        
161
        return $string;
162
    }
163
    
164
   /**
165
    * Retrieves the URL of the page in which the exception
166
    * was thrown, if applicable: in CLI context, this will
167
    * return an empty string.
168
    * 
169
    * @return string
170
    */
171
    public function getReferer() : string
172
    {
173
        return $this->referer;
174
    }
175
    
176
   /**
177
    * Whether the exception occurred in a command line context.
178
    * @return bool
179
    */
180
    public function isCommandLine() : bool
181
    {
182
        return $this->getContext() === self::CONTEXT_COMMAND_LINE;
183
    }
184
    
185
   /**
186
    * Whether the exception occurred during an http request.
187
    * @return bool
188
    */
189
    public function isWebRequest() : bool
190
    {
191
        return $this->getContext() === self::CONTEXT_WEB;
192
    }
193
    
194
   /**
195
    * Retrieves the context identifier, i.e. if the exception
196
    * occurred in a command line context or regular web request.
197
    * 
198
    * @return string
199
    * 
200
    * @see ConvertHelper_ThrowableInfo::isCommandLine()
201
    * @see ConvertHelper_ThrowableInfo::isWebRequest()
202
    * @see ConvertHelper_ThrowableInfo::CONTEXT_COMMAND_LINE
203
    * @see ConvertHelper_ThrowableInfo::CONTEXT_WEB
204
    */
205
    public function getContext() : string
206
    {
207
        return $this->context;
208
    }
209
    
210
   /**
211
    * Retrieves the date of the exception, and approximate time:
212
    * since exceptions do not store time, this is captured the 
213
    * moment the ThrowableInfo is created.
214
    * 
215
    * @return \DateTime
216
    */
217
    public function getDate() : \DateTime
218
    {
219
        return $this->date;
220
    }
221
    
222
   /**
223
    * Serializes all information on the exception to an
224
    * associative array. This can be saved (file, database, 
225
    * session...), and later be restored into a throwable
226
    * info instance using the fromSerialized() method.
227
    * 
228
    * @return array
229
    * @see ConvertHelper_ThrowableInfo::fromSerialized()
230
    */
231
    public function serialize() : array
232
    {
233
        $result = array(
234
            'message' => $this->getMessage(),
235
            'code' => $this->getCode(),
236
            'date' => $this->date->format('Y-m-d H:i:s'),
237
            'referer' => $this->getReferer(),
238
            'context' => $this->getContext(),
239
            'amountCalls' => $this->callsCount,
240
            'options' => $this->getOptions(),
241
            'calls' => array(),
242
            'previous' => null,
243
        );
244
        
245
        if($this->hasPrevious()) {
246
            $result['previous'] =  $this->previous->serialize();
247
        }
248
        
249
        foreach($this->calls as $call)
250
        {
251
            $result['calls'][] = $call->serialize(); 
252
        }
253
        
254
        return $result;
255
    }
256
257
   /**
258
    * Sets the maximum folder depth to show in the 
259
    * file paths, to avoid them being too long.
260
    * 
261
    * @param int $depth
262
    * @return ConvertHelper_ThrowableInfo
263
    */
264
    public function setFolderDepth(int $depth) : ConvertHelper_ThrowableInfo
265
    {
266
        return $this->setOption('folder-depth', $depth);
267
    }
268
    
269
   /**
270
    * Retrieves the current folder depth option value.
271
    * 
272
    * @return int
273
    * @see ConvertHelper_ThrowableInfo::setFolderDepth()
274
    */
275
    public function getFolderDepth() : int
276
    {
277
        $depth = $this->getOption('folder-depth');
278
        if(!empty($depth)) {
279
            return $depth;
280
        }
281
        
282
        return 2;
283
    }
284
    
285
   /**
286
    * Retrieves all function calls that led to the error.
287
    * @return ConvertHelper_ThrowableInfo_Call[]
288
    */
289
    public function getCalls()
290
    {
291
        return $this->calls;
292
    }
293
    
294
   /**
295
    * Returns the amount of function and method calls in the stack trace.
296
    * @return int
297
    */
298
    public function countCalls() : int
299
    {
300
        return $this->callsCount;
301
    }
302
    
303
    protected function parseSerialized(array $serialized) : void
304
    {
305
        $this->date = new \DateTime($serialized['date']);
306
        $this->code = $serialized['code'];
307
        $this->message = $serialized['message'];
308
        $this->referer = $serialized['referer'];
309
        $this->context = $serialized['context'];
310
        $this->callsCount = $serialized['amountCalls'];
311
        
312
        $this->setOptions($serialized['options']);
313
        
314
        if(!empty($serialized['previous']))
315
        {
316
            $this->previous = ConvertHelper_ThrowableInfo::fromSerialized($serialized['previous']);
317
        }
318
        
319
        foreach($serialized['calls'] as $def)
320
        {
321
            $this->calls[] = ConvertHelper_ThrowableInfo_Call::fromSerialized($this, $def);
322
        }
323
    }
324
    
325
    protected function parseException(\Throwable $e) : void
326
    {
327
        $this->date = new \DateTime();
328
        $this->message = $e->getMessage();
329
        $this->code = intval($e->getCode());
330
        
331
        if(!isset($_REQUEST['REQUEST_URI'])) {
332
            $this->context = self::CONTEXT_COMMAND_LINE;
333
        }
334
        
335
        $previous = $e->getPrevious();
336
        if(!empty($previous)) {
337
            $this->previous = ConvertHelper::throwable2info($previous);
338
        }
339
        
340
        if(isset($_SERVER['REQUEST_URI'])) {
341
            $this->referer = $_SERVER['REQUEST_URI'];
342
        }
343
        
344
        $trace = $e->getTrace();
345
        
346
        // add the origin file as entry
347
        array_unshift($trace, array(
348
            'file' => $e->getFile(),
349
            'line' => $e->getLine()
350
        ));
351
        
352
        $idx = 1;
353
        
354
        foreach($trace as $entry)
355
        {
356
            $this->calls[] = ConvertHelper_ThrowableInfo_Call::fromTrace($this, $idx, $entry);
357
            
358
            $idx++;
359
        }
360
        
361
        // we want the last function call first
362
        $this->calls = array_reverse($this->calls, false);
363
        
364
        $this->callsCount = count($this->calls);
365
    }
366
    
367
    public function __toString()
368
    {
369
        return $this->toString();
370
    }
371
}
372