Completed
Pull Request — v1 (#450)
by Denis
02:54
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.3183

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 5
cts 8
cp 0.625
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 4
nop 1
crap 6.3183
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 Whoops\Util\Misc;
10
11
class Inspector
12
{
13
    /**
14
     * @var \Throwable
15
     */
16
    private $exception;
17
18
    /**
19
     * @var \Whoops\Exception\FrameCollection
20
     */
21
    private $frames;
22
23
    /**
24
     * @var \Whoops\Exception\Inspector
25
     */
26
    private $previousExceptionInspector;
27
28
    /**
29
     * @param \Throwable $exception The exception to inspect
30
     */
31 3
    public function __construct($exception)
32
    {
33 3
        $this->exception = $exception;
34 3
    }
35
36
    /**
37
     * @return \Throwable
38
     */
39 3
    public function getException()
40
    {
41 3
        return $this->exception;
42
    }
43
44
    /**
45
     * @return string
46
     */
47 2
    public function getExceptionName()
48
    {
49 2
        return get_class($this->exception);
50
    }
51
52
    /**
53
     * @return string
54
     */
55
    public function getExceptionMessage()
56
    {
57
        return $this->exception->getMessage();
58
    }
59
60
    /**
61
     * Does the wrapped Exception has a previous Exception?
62
     * @return bool
63
     */
64 2
    public function hasPreviousException()
65
    {
66 2
        return $this->previousExceptionInspector || $this->exception->getPrevious();
67
    }
68
69
    /**
70
     * Returns an Inspector for a previous Exception, if any.
71
     * @todo   Clean this up a bit, cache stuff a bit better.
72
     * @return Inspector
73
     */
74 2
    public function getPreviousExceptionInspector()
75
    {
76 2
        if ($this->previousExceptionInspector === null) {
77 2
            $previousException = $this->exception->getPrevious();
78
79 2
            if ($previousException) {
80 1
                $this->previousExceptionInspector = new Inspector($previousException);
81 1
            }
82 2
        }
83
84 2
        return $this->previousExceptionInspector;
85
    }
86
87
    /**
88
     * Returns an iterator for the inspected exception's
89
     * frames.
90
     * @return \Whoops\Exception\FrameCollection
91
     */
92 3
    public function getFrames()
93
    {
94 3
        if ($this->frames === null) {
95 3
            $frames = $this->getTrace($this->exception);            
96
            
97
            // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428) 
98 3
            foreach ($frames as $k => $frame) {
99
                
100 3
                if (empty($frame['file'])) {
101
                    // Default values when file and line are missing
102 3
                    $file = '[internal]';
103 3
                    $line = 0;
104
                    
105 3
                    $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
106
                    
107 3
                    if ($this->isValidNextFrame($next_frame)) {
108
                        $file = $next_frame['file'];
109
                        $line = $next_frame['line'];
110
                    }
111
                    
112 3
                    $frames[$k]['file'] = $file;
113 3
                    $frames[$k]['line'] = $line;
114 3
                }
115
                
116 3
            }
117
            
118
            // Find latest non-error handling frame index ($i) used to remove error handling frames
119 3
            $i = 0;
120 3
            foreach ($frames as $k => $frame) {
121 3
                if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
122
                    $i = $k;
123
                }
124 3
            }
125
            
126
            // Remove error handling frames
127 3
            if ($i > 0) {
128
                array_splice($frames, 0, $i);               
129
            } 
130
            
131 3
            $firstFrame = $this->getFrameFromException($this->exception);
132 3
            array_unshift($frames, $firstFrame);
133
            
134 3
            $this->frames = new FrameCollection($frames);
135
136 3
            if ($previousInspector = $this->getPreviousExceptionInspector()) {
137
                // Keep outer frame on top of the inner one
138 1
                $outerFrames = $this->frames;
139 1
                $newFrames = clone $previousInspector->getFrames();
140
                // I assume it will always be set, but let's be safe
141 1
                if (isset($newFrames[0])) {
142 1
                    $newFrames[0]->addComment(
143 1
                        $previousInspector->getExceptionMessage(),
144
                        'Exception message:'
145 1
                    );
146 1
                }
147 1
                $newFrames->prependFrames($outerFrames->topDiff($newFrames));
0 ignored issues
show
Documentation introduced by
$outerFrames->topDiff($newFrames) is of type array<integer,array>, but the function expects a array<integer,object<Whoops\Exception\Frame>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
148 1
                $this->frames = $newFrames;
149 1
            }
150 3
        }
151
152 3
        return $this->frames;
153
    }
154
155
    /**
156
     * Gets the backgrace from an exception.
157
     *
158
     * If xdebug is installed
159
     *
160
     * @param  \Throwable $exception
0 ignored issues
show
Bug introduced by
There is no parameter named $exception. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
161
     * @return array
162
     */
163 1
    protected function getTrace($e)
164
    {
165 1
        $traces = $e->getTrace();
166
167
        // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
168 1
        if (!$e instanceof \ErrorException) {
169 1
            return $traces;
170
        }
171
172
        if (!Misc::isLevelFatal($e->getSeverity())) {
173
            return $traces;
174
        }
175
176
        if (!extension_loaded('xdebug') || !xdebug_is_enabled()) {
177
            return [];
178
        }
179
180
        // Use xdebug to get the full stack trace and remove the shutdown handler stack trace
181
        $stack = array_reverse(xdebug_get_function_stack());
182
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
183
        $traces = array_diff_key($stack, $trace);
184
185
        return $traces;
186
    }
187
188
    /**
189
     * Given an exception, generates an array in the format
190
     * generated by Exception::getTrace()
191
     * @param  \Throwable $exception
192
     * @return array
193
     */
194 1
    protected function getFrameFromException($exception)
195
    {
196
        return [
197 1
            'file'  => $exception->getFile(),
198 1
            'line'  => $exception->getLine(),
199 1
            'class' => get_class($exception),
200
            'args'  => [
201 1
                $exception->getMessage(),
202 1
            ],
203 1
        ];
204
    }
205
206
    /**
207
     * Given an error, generates an array in the format
208
     * generated by ErrorException
209
     * @param  ErrorException $exception
210
     * @return array
211
     */
212
    protected function getFrameFromError(ErrorException $exception)
213
    {
214
        return [
215
            'file'  => $exception->getFile(),
216
            'line'  => $exception->getLine(),
217
            'class' => null,
218
            'args'  => [],
219
        ];
220
    }
221
    
222
    /**
223
     * Determine if the frame can be used to fill in previous frame's missing info
224
     * happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
225
     * 
226
     * @param array $frame
227
     * @return bool
228
     */
229 1
    protected function isValidNextFrame(array $frame)
230
    {
231 1
        if (empty($frame['file'])) {
232
            return false;
233
        }
234
        
235 1
        if (empty($frame['line'])) {
236
            return false;
237
        }
238
        
239 1
        if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
240 1
            return false;
241
        }
242
        
243
        return true;
244
    }
245
}
246