Completed
Push — master ( fa28f0...71ba35 )
by Denis
12s
created

Inspector::getFrames()   C

Complexity

Conditions 12
Paths 7

Size

Total Lines 60
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 12.7162

Importance

Changes 0
Metric Value
dl 0
loc 60
ccs 34
cts 41
cp 0.8293
rs 6.366
c 0
b 0
f 0
cc 12
eloc 32
nc 7
nop 0
crap 12.7162

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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->extractDocrefUrl($this->exception->getMessage())['message'];
58
    }
59
60
    /**
61
     * Returns a url to the php-manual related to the underlying error - when available.
62
     *
63
     * @return string|null
64
     */
65
    public function getExceptionDocrefUrl()
66
    {
67
        return $this->extractDocrefUrl($this->exception->getMessage())['url'];
68
    }
69
70
    private function extractDocrefUrl($message)
71
    {
72
        $docref = [
73
            'message' => $message,
74
            'url' => null,
75
        ];
76
77
        // php embbeds urls to the manual into the Exception message with the following ini-settings defined
78
        // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
79
        if (!ini_get('html_errors') || !ini_get('docref_root')) {
80
            return $docref;
81
        }
82
83
        $pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
84
        if (preg_match($pattern, $message, $matches)) {
85
            // -> strip those automatically generated links from the exception message
86
            $docref['message'] = preg_replace($pattern, '', $message, 1);
87
            $docref['url'] = $matches[1];
88
        }
89
90
        return $docref;
91
    }
92
93
    /**
94
     * Does the wrapped Exception has a previous Exception?
95
     * @return bool
96
     */
97 2
    public function hasPreviousException()
98
    {
99 2
        return $this->previousExceptionInspector || $this->exception->getPrevious();
100
    }
101
102
    /**
103
     * Returns an Inspector for a previous Exception, if any.
104
     * @todo   Clean this up a bit, cache stuff a bit better.
105
     * @return Inspector
106
     */
107 2
    public function getPreviousExceptionInspector()
108
    {
109 2
        if ($this->previousExceptionInspector === null) {
110 2
            $previousException = $this->exception->getPrevious();
111
112 2
            if ($previousException) {
113 1
                $this->previousExceptionInspector = new Inspector($previousException);
114 1
            }
115 2
        }
116
117 2
        return $this->previousExceptionInspector;
118
    }
119
120
    /**
121
     * Returns an iterator for the inspected exception's
122
     * frames.
123
     * @return \Whoops\Exception\FrameCollection
124
     */
125 3
    public function getFrames()
126
    {
127 3
        if ($this->frames === null) {
128 3
            $frames = $this->getTrace($this->exception);
129
130
            // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
131 3
            foreach ($frames as $k => $frame) {
132 3
                if (empty($frame['file'])) {
133
                    // Default values when file and line are missing
134 3
                    $file = '[internal]';
135 3
                    $line = 0;
136
137 3
                    $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
138
139 3
                    if ($this->isValidNextFrame($next_frame)) {
140
                        $file = $next_frame['file'];
141
                        $line = $next_frame['line'];
142
                    }
143
144 3
                    $frames[$k]['file'] = $file;
145 3
                    $frames[$k]['line'] = $line;
146 3
                }
147 3
            }
148
149
            // Find latest non-error handling frame index ($i) used to remove error handling frames
150 3
            $i = 0;
151 3
            foreach ($frames as $k => $frame) {
152 3
                if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
153
                    $i = $k;
154
                }
155 3
            }
156
157
            // Remove error handling frames
158 3
            if ($i > 0) {
159
                array_splice($frames, 0, $i);
160
            }
161
162 3
            $firstFrame = $this->getFrameFromException($this->exception);
163 3
            array_unshift($frames, $firstFrame);
164
165 3
            $this->frames = new FrameCollection($frames);
166
167 3
            if ($previousInspector = $this->getPreviousExceptionInspector()) {
168
                // Keep outer frame on top of the inner one
169 1
                $outerFrames = $this->frames;
170 1
                $newFrames = clone $previousInspector->getFrames();
171
                // I assume it will always be set, but let's be safe
172 1
                if (isset($newFrames[0])) {
173 1
                    $newFrames[0]->addComment(
174 1
                        $previousInspector->getExceptionMessage(),
175
                        'Exception message:'
176 1
                    );
177 1
                }
178 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...
179 1
                $this->frames = $newFrames;
180 1
            }
181 3
        }
182
183 3
        return $this->frames;
184
    }
185
186
    /**
187
     * Gets the backtrace from an exception.
188
     *
189
     * If xdebug is installed
190
     *
191
     * @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...
192
     * @return array
193
     */
194 1
    protected function getTrace($e)
195
    {
196 1
        $traces = $e->getTrace();
197
198
        // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
199 1
        if (!$e instanceof \ErrorException) {
200 1
            return $traces;
201
        }
202
203
        if (!Misc::isLevelFatal($e->getSeverity())) {
204
            return $traces;
205
        }
206
207
        if (!extension_loaded('xdebug') || !xdebug_is_enabled()) {
208
            return [];
209
        }
210
211
        // Use xdebug to get the full stack trace and remove the shutdown handler stack trace
212
        $stack = array_reverse(xdebug_get_function_stack());
213
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
214
        $traces = array_diff_key($stack, $trace);
215
216
        return $traces;
217
    }
218
219
    /**
220
     * Given an exception, generates an array in the format
221
     * generated by Exception::getTrace()
222
     * @param  \Throwable $exception
223
     * @return array
224
     */
225 1
    protected function getFrameFromException($exception)
226
    {
227
        return [
228 1
            'file'  => $exception->getFile(),
229 1
            'line'  => $exception->getLine(),
230 1
            'class' => get_class($exception),
231
            'args'  => [
232 1
                $exception->getMessage(),
233 1
            ],
234 1
        ];
235
    }
236
237
    /**
238
     * Given an error, generates an array in the format
239
     * generated by ErrorException
240
     * @param  ErrorException $exception
241
     * @return array
242
     */
243
    protected function getFrameFromError(ErrorException $exception)
244
    {
245
        return [
246
            'file'  => $exception->getFile(),
247
            'line'  => $exception->getLine(),
248
            'class' => null,
249
            'args'  => [],
250
        ];
251
    }
252
253
    /**
254
     * Determine if the frame can be used to fill in previous frame's missing info
255
     * happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
256
     *
257
     * @param array $frame
258
     * @return bool
259
     */
260 1
    protected function isValidNextFrame(array $frame)
261
    {
262 1
        if (empty($frame['file'])) {
263
            return false;
264
        }
265
266 1
        if (empty($frame['line'])) {
267
            return false;
268
        }
269
270 1
        if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
271 1
            return false;
272
        }
273
274
        return true;
275
    }
276
}
277