Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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
 
688 lars 9
use Whoops\Inspector\InspectorFactory;
10
use Whoops\Inspector\InspectorInterface;
148 lars 11
use Whoops\Util\Misc;
12
 
688 lars 13
class Inspector implements InspectorInterface
148 lars 14
{
15
    /**
16
     * @var \Throwable
17
     */
18
    private $exception;
19
 
20
    /**
21
     * @var \Whoops\Exception\FrameCollection
22
     */
23
    private $frames;
24
 
25
    /**
26
     * @var \Whoops\Exception\Inspector
27
     */
28
    private $previousExceptionInspector;
29
 
30
    /**
31
     * @var \Throwable[]
32
     */
33
    private $previousExceptions;
34
 
35
    /**
688 lars 36
     * @var \Whoops\Inspector\InspectorFactoryInterface|null
37
     */
38
    protected $inspectorFactory;
39
 
40
    /**
148 lars 41
     * @param \Throwable $exception The exception to inspect
688 lars 42
     * @param \Whoops\Inspector\InspectorFactoryInterface $factory
148 lars 43
     */
688 lars 44
    public function __construct($exception, $factory = null)
148 lars 45
    {
46
        $this->exception = $exception;
688 lars 47
        $this->inspectorFactory = $factory ?: new InspectorFactory();
148 lars 48
    }
49
 
50
    /**
51
     * @return \Throwable
52
     */
53
    public function getException()
54
    {
55
        return $this->exception;
56
    }
57
 
58
    /**
59
     * @return string
60
     */
61
    public function getExceptionName()
62
    {
63
        return get_class($this->exception);
64
    }
65
 
66
    /**
67
     * @return string
68
     */
69
    public function getExceptionMessage()
70
    {
71
        return $this->extractDocrefUrl($this->exception->getMessage())['message'];
72
    }
73
 
74
    /**
75
     * @return string[]
76
     */
77
    public function getPreviousExceptionMessages()
78
    {
79
        return array_map(function ($prev) {
80
            /** @var \Throwable $prev */
81
            return $this->extractDocrefUrl($prev->getMessage())['message'];
82
        }, $this->getPreviousExceptions());
83
    }
84
 
85
    /**
86
     * @return int[]
87
     */
88
    public function getPreviousExceptionCodes()
89
    {
90
        return array_map(function ($prev) {
91
            /** @var \Throwable $prev */
92
            return $prev->getCode();
93
        }, $this->getPreviousExceptions());
94
    }
95
 
96
    /**
97
     * Returns a url to the php-manual related to the underlying error - when available.
98
     *
99
     * @return string|null
100
     */
101
    public function getExceptionDocrefUrl()
102
    {
103
        return $this->extractDocrefUrl($this->exception->getMessage())['url'];
104
    }
105
 
106
    private function extractDocrefUrl($message)
107
    {
108
        $docref = [
109
            'message' => $message,
110
            'url' => null,
111
        ];
112
 
113
        // php embbeds urls to the manual into the Exception message with the following ini-settings defined
114
        // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
115
        if (!ini_get('html_errors') || !ini_get('docref_root')) {
116
            return $docref;
117
        }
118
 
119
        $pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
120
        if (preg_match($pattern, $message, $matches)) {
121
            // -> strip those automatically generated links from the exception message
122
            $docref['message'] = preg_replace($pattern, '', $message, 1);
123
            $docref['url'] = $matches[1];
124
        }
125
 
126
        return $docref;
127
    }
128
 
129
    /**
130
     * Does the wrapped Exception has a previous Exception?
131
     * @return bool
132
     */
133
    public function hasPreviousException()
134
    {
135
        return $this->previousExceptionInspector || $this->exception->getPrevious();
136
    }
137
 
138
    /**
139
     * Returns an Inspector for a previous Exception, if any.
140
     * @todo   Clean this up a bit, cache stuff a bit better.
141
     * @return Inspector
142
     */
143
    public function getPreviousExceptionInspector()
144
    {
145
        if ($this->previousExceptionInspector === null) {
146
            $previousException = $this->exception->getPrevious();
147
 
148
            if ($previousException) {
688 lars 149
                $this->previousExceptionInspector = $this->inspectorFactory->create($previousException);
148 lars 150
            }
151
        }
152
 
153
        return $this->previousExceptionInspector;
154
    }
155
 
156
 
157
    /**
158
     * Returns an array of all previous exceptions for this inspector's exception
159
     * @return \Throwable[]
160
     */
161
    public function getPreviousExceptions()
162
    {
163
        if ($this->previousExceptions === null) {
164
            $this->previousExceptions = [];
165
 
166
            $prev = $this->exception->getPrevious();
167
            while ($prev !== null) {
168
                $this->previousExceptions[] = $prev;
169
                $prev = $prev->getPrevious();
170
            }
171
        }
172
 
173
        return $this->previousExceptions;
174
    }
175
 
176
    /**
177
     * Returns an iterator for the inspected exception's
178
     * frames.
688 lars 179
     *
180
     * @param array<callable> $frameFilters
181
     *
148 lars 182
     * @return \Whoops\Exception\FrameCollection
183
     */
688 lars 184
    public function getFrames(array $frameFilters = [])
148 lars 185
    {
186
        if ($this->frames === null) {
187
            $frames = $this->getTrace($this->exception);
188
 
189
            // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
190
            foreach ($frames as $k => $frame) {
191
                if (empty($frame['file'])) {
192
                    // Default values when file and line are missing
193
                    $file = '[internal]';
194
                    $line = 0;
195
 
196
                    $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
197
 
198
                    if ($this->isValidNextFrame($next_frame)) {
199
                        $file = $next_frame['file'];
200
                        $line = $next_frame['line'];
201
                    }
202
 
203
                    $frames[$k]['file'] = $file;
204
                    $frames[$k]['line'] = $line;
205
                }
206
            }
207
 
208
            // Find latest non-error handling frame index ($i) used to remove error handling frames
209
            $i = 0;
210
            foreach ($frames as $k => $frame) {
211
                if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
212
                    $i = $k;
213
                }
214
            }
215
 
216
            // Remove error handling frames
217
            if ($i > 0) {
218
                array_splice($frames, 0, $i);
219
            }
220
 
221
            $firstFrame = $this->getFrameFromException($this->exception);
222
            array_unshift($frames, $firstFrame);
223
 
224
            $this->frames = new FrameCollection($frames);
225
 
226
            if ($previousInspector = $this->getPreviousExceptionInspector()) {
227
                // Keep outer frame on top of the inner one
228
                $outerFrames = $this->frames;
229
                $newFrames = clone $previousInspector->getFrames();
230
                // I assume it will always be set, but let's be safe
231
                if (isset($newFrames[0])) {
232
                    $newFrames[0]->addComment(
233
                        $previousInspector->getExceptionMessage(),
234
                        'Exception message:'
235
                    );
236
                }
237
                $newFrames->prependFrames($outerFrames->topDiff($newFrames));
238
                $this->frames = $newFrames;
239
            }
688 lars 240
 
241
            // Apply frame filters callbacks on the frames stack
242
            if (!empty($frameFilters)) {
243
                foreach ($frameFilters as $filterCallback) {
244
                    $this->frames->filter($filterCallback);
245
                }
246
            }
148 lars 247
        }
248
 
249
        return $this->frames;
250
    }
251
 
252
    /**
253
     * Gets the backtrace from an exception.
254
     *
255
     * If xdebug is installed
256
     *
257
     * @param \Throwable $e
258
     * @return array
259
     */
260
    protected function getTrace($e)
261
    {
262
        $traces = $e->getTrace();
263
 
264
        // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
265
        if (!$e instanceof \ErrorException) {
266
            return $traces;
267
        }
268
 
269
        if (!Misc::isLevelFatal($e->getSeverity())) {
270
            return $traces;
271
        }
272
 
273
        if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) {
274
            return $traces;
275
        }
276
 
277
        // Use xdebug to get the full stack trace and remove the shutdown handler stack trace
278
        $stack = array_reverse(xdebug_get_function_stack());
279
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
280
        $traces = array_diff_key($stack, $trace);
281
 
282
        return $traces;
283
    }
284
 
285
    /**
286
     * Given an exception, generates an array in the format
287
     * generated by Exception::getTrace()
288
     * @param  \Throwable $exception
289
     * @return array
290
     */
291
    protected function getFrameFromException($exception)
292
    {
293
        return [
294
            'file'  => $exception->getFile(),
295
            'line'  => $exception->getLine(),
296
            'class' => get_class($exception),
297
            'args'  => [
298
                $exception->getMessage(),
299
            ],
300
        ];
301
    }
302
 
303
    /**
304
     * Given an error, generates an array in the format
305
     * generated by ErrorException
306
     * @param  ErrorException $exception
307
     * @return array
308
     */
309
    protected function getFrameFromError(ErrorException $exception)
310
    {
311
        return [
312
            'file'  => $exception->getFile(),
313
            'line'  => $exception->getLine(),
314
            'class' => null,
315
            'args'  => [],
316
        ];
317
    }
318
 
319
    /**
320
     * Determine if the frame can be used to fill in previous frame's missing info
321
     * happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
322
     *
323
     * @return bool
324
     */
325
    protected function isValidNextFrame(array $frame)
326
    {
327
        if (empty($frame['file'])) {
328
            return false;
329
        }
330
 
331
        if (empty($frame['line'])) {
332
            return false;
333
        }
334
 
335
        if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
336
            return false;
337
        }
338
 
339
        return true;
340
    }
341
}