Completed
Push — master ( fc553f...577ccd )
by Denis
09:01 queued 06:53
created

Inspector::isValidNextFrame()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 6.3184

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 16
ccs 5
cts 8
cp 0.625
rs 8.8571
cc 5
eloc 8
nc 4
nop 1
crap 6.3184
1
<?php
2
/**
3
 * Whoops - php errors for cool kids
4
 * @author Filipe Dobreira <http://github.com/filp>
5
 */
6
7
namespace Whoops\Exception;
8
9
use Exception;
10
use Whoops\Util\Misc;
11
12
class Inspector
13
{
14
    /**
15
     * @var Exception
16
     */
17
    private $exception;
18
19
    /**
20
     * @var \Whoops\Exception\FrameCollection
21
     */
22
    private $frames;
23
24
    /**
25
     * @var \Whoops\Exception\Inspector
26
     */
27
    private $previousExceptionInspector;
28
29
    /**
30
     * @param Exception $exception The exception to inspect
31
     */
32 4
    public function __construct(Exception $exception)
33
    {
34 4
        $this->exception = $exception;
35 4
    }
36
37
    /**
38
     * @return Exception
39
     */
40 3
    public function getException()
41
    {
42 3
        return $this->exception;
43
    }
44
45
    /**
46
     * @return string
47
     */
48 2
    public function getExceptionName()
49
    {
50 2
        return get_class($this->exception);
51
    }
52
53
    /**
54
     * @return string
55
     */
56
    public function getExceptionMessage()
57
    {
58
        return $this->exception->getMessage();
59
    }
60
61
    /**
62
     * Does the wrapped Exception has a previous Exception?
63
     * @return bool
64
     */
65 2
    public function hasPreviousException()
66
    {
67 2
        return $this->previousExceptionInspector || $this->exception->getPrevious();
68
    }
69
70
    /**
71
     * Returns an Inspector for a previous Exception, if any.
72
     * @todo   Clean this up a bit, cache stuff a bit better.
73
     * @return Inspector
74
     */
75 2
    public function getPreviousExceptionInspector()
76
    {
77 2
        if ($this->previousExceptionInspector === null) {
78 2
            $previousException = $this->exception->getPrevious();
79
80 2
            if ($previousException) {
81 1
                $this->previousExceptionInspector = new Inspector($previousException);
82 1
            }
83 2
        }
84
85 2
        return $this->previousExceptionInspector;
86
    }
87
88
    /**
89
     * Returns an iterator for the inspected exception's
90
     * frames.
91
     * @return \Whoops\Exception\FrameCollection
92
     */
93 3
    public function getFrames()
94
    {
95 3
        if ($this->frames === null) {
96 3
            $frames = $this->getTrace($this->exception);            
97
            
98
            // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428) 
99 3
            foreach ($frames as $k => $frame) {
100
                
101 3
                if (empty($frame['file'])) {
102
                    // Default values when file and line are missing
103 3
                    $file = '[internal]';
104 3
                    $line = 0;
105
                    
106 3
                    $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : array();
107
                    
108 3
                    if ($this->isValidNextFrame($next_frame)) {
109
                        $file = $next_frame['file'];
110
                        $line = $next_frame['line'];
111
                    }
112
                    
113 3
                    $frames[$k]['file'] = $file;
114 3
                    $frames[$k]['line'] = $line;
115 3
                }
116
                
117 3
            }
118
            
119
            // Find latest non-error handling frame index ($i) used to remove error handling frames
120 3
            $i = 0;
121 3
            foreach ($frames as $k => $frame) {
122 3
                if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
123
                    $i = $k;
124
                }
125 3
            }
126
            
127
            // Remove error handling frames
128 3
            if ($i > 0) {
129
                array_splice($frames, 0, $i);               
130
            } 
131
            
132 3
            $firstFrame = $this->getFrameFromException($this->exception);
133 3
            array_unshift($frames, $firstFrame);
134
            
135 3
            $this->frames = new FrameCollection($frames);
136
137 3
            if ($previousInspector = $this->getPreviousExceptionInspector()) {
138
                // Keep outer frame on top of the inner one
139 1
                $outerFrames = $this->frames;
140 1
                $newFrames = clone $previousInspector->getFrames();
141
                // I assume it will always be set, but let's be safe
142 1
                if (isset($newFrames[0])) {
143 1
                    $newFrames[0]->addComment(
144 1
                        $previousInspector->getExceptionMessage(),
145
                        'Exception message:'
146 1
                    );
147 1
                }
148 1
                $newFrames->prependFrames($outerFrames->topDiff($newFrames));
149 1
                $this->frames = $newFrames;
150 1
            }
151 3
        }
152
153 3
        return $this->frames;
154
    }
155
156
    /**
157
     * Gets the backgrace from an exception.
158
     *
159
     * If xdebug is installed
160
     *
161
     * @param Exception $e
162
     * @return array
163
     */
164 1
    protected function getTrace(\Exception $e)
165
    {
166 1
        $traces = $e->getTrace();
167
168
        // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
169 1
        if (!$e instanceof \ErrorException) {
170 1
            return $traces;
171
        }
172
173
        if (!Misc::isLevelFatal($e->getSeverity())) {
174
            return $traces;
175
        }
176
177
        if (!extension_loaded('xdebug') || !xdebug_is_enabled()) {
178
            return array();
179
        }
180
181
        // Use xdebug to get the full stack trace and remove the shutdown handler stack trace
182
        $stack = array_reverse(xdebug_get_function_stack());
183
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
184
        $traces = array_diff_key($stack, $trace);
185
186
        return $traces;
187
    }
188
189
    /**
190
     * Given an exception, generates an array in the format
191
     * generated by Exception::getTrace()
192
     * @param  Exception $exception
193
     * @return array
194
     */
195 1
    protected function getFrameFromException(Exception $exception)
196
    {
197
        return array(
198 1
            'file'  => $exception->getFile(),
199 1
            'line'  => $exception->getLine(),
200 1
            'class' => get_class($exception),
201
            'args'  => array(
202 1
                $exception->getMessage(),
203 1
            ),
204 1
        );
205
    }
206
207
    /**
208
     * Given an error, generates an array in the format
209
     * generated by ErrorException
210
     * @param  ErrorException $exception
211
     * @return array
212
     */
213
    protected function getFrameFromError(ErrorException $exception)
214
    {
215
        return array(
216
            'file'  => $exception->getFile(),
217
            'line'  => $exception->getLine(),
218
            'class' => null,
219
            'args'  => array(),
220
        );
221
    }
222
    
223
    /**
224
     * Determine if the frame can be used to fill in previous frame's missing info
225
     * happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
226
     * 
227
     * @param array $frame
228
     * @return bool
229
     */
230 1
    protected function isValidNextFrame(array $frame)
231
    {
232 1
        if (empty($frame['file'])) {
233
            return false;
234
        }
235
        
236 1
        if (empty($frame['line'])) {
237
            return false;
238
        }
239
        
240 1
        if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
241 1
            return false;
242
        }
243
        
244
        return true;
245
    }
246
}
247