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;
8
 
9
use InvalidArgumentException;
10
use Throwable;
11
use Whoops\Exception\ErrorException;
12
use Whoops\Handler\CallbackHandler;
13
use Whoops\Handler\Handler;
14
use Whoops\Handler\HandlerInterface;
688 lars 15
use Whoops\Inspector\CallableInspectorFactory;
16
use Whoops\Inspector\InspectorFactory;
17
use Whoops\Inspector\InspectorFactoryInterface;
18
use Whoops\Inspector\InspectorInterface;
148 lars 19
use Whoops\Util\Misc;
20
use Whoops\Util\SystemFacade;
21
 
22
final class Run implements RunInterface
23
{
24
    /**
25
     * @var bool
26
     */
27
    private $isRegistered;
28
 
29
    /**
30
     * @var bool
31
     */
32
    private $allowQuit       = true;
33
 
34
    /**
35
     * @var bool
36
     */
37
    private $sendOutput      = true;
38
 
39
    /**
40
     * @var integer|false
41
     */
42
    private $sendHttpCode    = 500;
43
 
44
    /**
45
     * @var integer|false
46
     */
47
    private $sendExitCode    = 1;
48
 
49
    /**
50
     * @var HandlerInterface[]
51
     */
52
    private $handlerStack = [];
53
 
54
    /**
55
     * @var array
56
     * @psalm-var list<array{patterns: string, levels: int}>
57
     */
58
    private $silencedPatterns = [];
59
 
60
    /**
61
     * @var SystemFacade
62
     */
63
    private $system;
64
 
65
    /**
66
     * In certain scenarios, like in shutdown handler, we can not throw exceptions.
67
     *
68
     * @var bool
69
     */
70
    private $canThrowExceptions = true;
71
 
688 lars 72
    /**
73
     * The inspector factory to create inspectors.
74
     *
75
     * @var InspectorFactoryInterface
76
     */
77
    private $inspectorFactory;
78
 
79
    /**
80
     * @var array<callable>
81
     */
82
    private $frameFilters = [];
83
 
148 lars 84
    public function __construct(SystemFacade $system = null)
85
    {
86
        $this->system = $system ?: new SystemFacade;
688 lars 87
        $this->inspectorFactory = new InspectorFactory();
148 lars 88
    }
89
 
90
    /**
91
     * Explicitly request your handler runs as the last of all currently registered handlers.
92
     *
93
     * @param callable|HandlerInterface $handler
94
     *
95
     * @return Run
96
     */
97
    public function appendHandler($handler)
98
    {
99
        array_unshift($this->handlerStack, $this->resolveHandler($handler));
100
        return $this;
101
    }
102
 
103
    /**
104
     * Explicitly request your handler runs as the first of all currently registered handlers.
105
     *
106
     * @param callable|HandlerInterface $handler
107
     *
108
     * @return Run
109
     */
110
    public function prependHandler($handler)
111
    {
112
        return $this->pushHandler($handler);
113
    }
114
 
115
    /**
116
     * Register your handler as the last of all currently registered handlers (to be executed first).
117
     * Prefer using appendHandler and prependHandler for clarity.
118
     *
119
     * @param callable|HandlerInterface $handler
120
     *
121
     * @return Run
122
     *
123
     * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
124
     */
125
    public function pushHandler($handler)
126
    {
127
        $this->handlerStack[] = $this->resolveHandler($handler);
128
        return $this;
129
    }
130
 
131
    /**
132
     * Removes and returns the last handler pushed to the handler stack.
133
     *
134
     * @see Run::removeFirstHandler(), Run::removeLastHandler()
135
     *
136
     * @return HandlerInterface|null
137
     */
138
    public function popHandler()
139
    {
140
        return array_pop($this->handlerStack);
141
    }
142
 
143
    /**
144
     * Removes the first handler.
145
     *
146
     * @return void
147
     */
148
    public function removeFirstHandler()
149
    {
150
        array_pop($this->handlerStack);
151
    }
152
 
153
    /**
154
     * Removes the last handler.
155
     *
156
     * @return void
157
     */
158
    public function removeLastHandler()
159
    {
160
        array_shift($this->handlerStack);
161
    }
162
 
163
    /**
164
     * Returns an array with all handlers, in the order they were added to the stack.
165
     *
166
     * @return array
167
     */
168
    public function getHandlers()
169
    {
170
        return $this->handlerStack;
171
    }
172
 
173
    /**
174
     * Clears all handlers in the handlerStack, including the default PrettyPage handler.
175
     *
176
     * @return Run
177
     */
178
    public function clearHandlers()
179
    {
180
        $this->handlerStack = [];
181
        return $this;
182
    }
183
 
688 lars 184
    public function getFrameFilters()
185
    {
186
        return $this->frameFilters;
187
    }
188
 
189
    public function clearFrameFilters()
190
    {
191
        $this->frameFilters = [];
192
        return $this;
193
    }
194
 
148 lars 195
    /**
196
     * Registers this instance as an error handler.
197
     *
198
     * @return Run
199
     */
200
    public function register()
201
    {
202
        if (!$this->isRegistered) {
203
            // Workaround PHP bug 42098
204
            // https://bugs.php.net/bug.php?id=42098
205
            class_exists("\\Whoops\\Exception\\ErrorException");
206
            class_exists("\\Whoops\\Exception\\FrameCollection");
207
            class_exists("\\Whoops\\Exception\\Frame");
208
            class_exists("\\Whoops\\Exception\\Inspector");
688 lars 209
            class_exists("\\Whoops\\Inspector\\InspectorFactory");
148 lars 210
 
211
            $this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
212
            $this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
213
            $this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
214
 
215
            $this->isRegistered = true;
216
        }
217
 
218
        return $this;
219
    }
220
 
221
    /**
222
     * Unregisters all handlers registered by this Whoops\Run instance.
223
     *
224
     * @return Run
225
     */
226
    public function unregister()
227
    {
228
        if ($this->isRegistered) {
229
            $this->system->restoreExceptionHandler();
230
            $this->system->restoreErrorHandler();
231
 
232
            $this->isRegistered = false;
233
        }
234
 
235
        return $this;
236
    }
237
 
238
    /**
239
     * Should Whoops allow Handlers to force the script to quit?
240
     *
241
     * @param bool|int $exit
242
     *
243
     * @return bool
244
     */
245
    public function allowQuit($exit = null)
246
    {
247
        if (func_num_args() == 0) {
248
            return $this->allowQuit;
249
        }
250
 
251
        return $this->allowQuit = (bool) $exit;
252
    }
253
 
254
    /**
255
     * Silence particular errors in particular files.
256
     *
257
     * @param array|string $patterns List or a single regex pattern to match.
258
     * @param int          $levels   Defaults to E_STRICT | E_DEPRECATED.
259
     *
260
     * @return Run
261
     */
262
    public function silenceErrorsInPaths($patterns, $levels = 10240)
263
    {
264
        $this->silencedPatterns = array_merge(
265
            $this->silencedPatterns,
266
            array_map(
267
                function ($pattern) use ($levels) {
268
                    return [
269
                        "pattern" => $pattern,
270
                        "levels" => $levels,
271
                    ];
272
                },
273
                (array) $patterns
274
            )
275
        );
276
 
277
        return $this;
278
    }
279
 
280
    /**
281
     * Returns an array with silent errors in path configuration.
282
     *
283
     * @return array
284
     */
285
    public function getSilenceErrorsInPaths()
286
    {
287
        return $this->silencedPatterns;
288
    }
289
 
290
    /**
291
     * Should Whoops send HTTP error code to the browser if possible?
292
     * Whoops will by default send HTTP code 500, but you may wish to
293
     * use 502, 503, or another 5xx family code.
294
     *
295
     * @param bool|int $code
296
     *
297
     * @return int|false
298
     *
299
     * @throws InvalidArgumentException
300
     */
301
    public function sendHttpCode($code = null)
302
    {
303
        if (func_num_args() == 0) {
304
            return $this->sendHttpCode;
305
        }
306
 
307
        if (!$code) {
308
            return $this->sendHttpCode = false;
309
        }
310
 
311
        if ($code === true) {
312
            $code = 500;
313
        }
314
 
315
        if ($code < 400 || 600 <= $code) {
316
            throw new InvalidArgumentException(
317
                "Invalid status code '$code', must be 4xx or 5xx"
318
            );
319
        }
320
 
321
        return $this->sendHttpCode = $code;
322
    }
323
 
324
    /**
325
     * Should Whoops exit with a specific code on the CLI if possible?
326
     * Whoops will exit with 1 by default, but you can specify something else.
327
     *
328
     * @param int $code
329
     *
330
     * @return int
331
     *
332
     * @throws InvalidArgumentException
333
     */
334
    public function sendExitCode($code = null)
335
    {
336
        if (func_num_args() == 0) {
337
            return $this->sendExitCode;
338
        }
339
 
340
        if ($code < 0 || 255 <= $code) {
341
            throw new InvalidArgumentException(
342
                "Invalid status code '$code', must be between 0 and 254"
343
            );
344
        }
345
 
346
        return $this->sendExitCode = (int) $code;
347
    }
348
 
349
    /**
350
     * Should Whoops push output directly to the client?
351
     * If this is false, output will be returned by handleException.
352
     *
353
     * @param bool|int $send
354
     *
355
     * @return bool
356
     */
357
    public function writeToOutput($send = null)
358
    {
359
        if (func_num_args() == 0) {
360
            return $this->sendOutput;
361
        }
362
 
363
        return $this->sendOutput = (bool) $send;
364
    }
365
 
366
    /**
367
     * Handles an exception, ultimately generating a Whoops error page.
368
     *
369
     * @param Throwable $exception
370
     *
371
     * @return string Output generated by handlers.
372
     */
373
    public function handleException($exception)
374
    {
375
        // Walk the registered handlers in the reverse order
376
        // they were registered, and pass off the exception
377
        $inspector = $this->getInspector($exception);
378
 
379
        // Capture output produced while handling the exception,
380
        // we might want to send it straight away to the client,
381
        // or return it silently.
382
        $this->system->startOutputBuffering();
383
 
384
        // Just in case there are no handlers:
385
        $handlerResponse = null;
386
        $handlerContentType = null;
387
 
388
        try {
389
            foreach (array_reverse($this->handlerStack) as $handler) {
390
                $handler->setRun($this);
391
                $handler->setInspector($inspector);
392
                $handler->setException($exception);
393
 
394
                // The HandlerInterface does not require an Exception passed to handle()
395
                // and neither of our bundled handlers use it.
396
                // However, 3rd party handlers may have already relied on this parameter,
397
                // and removing it would be possibly breaking for users.
398
                $handlerResponse = $handler->handle($exception);
399
 
400
                // Collect the content type for possible sending in the headers.
401
                $handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
402
 
403
                if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
404
                    // The Handler has handled the exception in some way, and
405
                    // wishes to quit execution (Handler::QUIT), or skip any
406
                    // other handlers (Handler::LAST_HANDLER). If $this->allowQuit
407
                    // is false, Handler::QUIT behaves like Handler::LAST_HANDLER
408
                    break;
409
                }
410
            }
411
 
412
            $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
413
        } finally {
414
            $output = $this->system->cleanOutputBuffer();
415
        }
416
 
417
        // If we're allowed to, send output generated by handlers directly
418
        // to the output, otherwise, and if the script doesn't quit, return
419
        // it so that it may be used by the caller
420
        if ($this->writeToOutput()) {
421
            // @todo Might be able to clean this up a bit better
422
            if ($willQuit) {
423
                // Cleanup all other output buffers before sending our output:
424
                while ($this->system->getOutputBufferLevel() > 0) {
425
                    $this->system->endOutputBuffering();
426
                }
427
 
428
                // Send any headers if needed:
429
                if (Misc::canSendHeaders() && $handlerContentType) {
430
                    header("Content-Type: {$handlerContentType}");
431
                }
432
            }
433
 
434
            $this->writeToOutputNow($output);
435
        }
436
 
437
        if ($willQuit) {
438
            // HHVM fix for https://github.com/facebook/hhvm/issues/4055
439
            $this->system->flushOutputBuffer();
440
 
441
            $this->system->stopExecution(
442
                $this->sendExitCode()
443
            );
444
        }
445
 
446
        return $output;
447
    }
448
 
449
    /**
450
     * Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
451
     *
452
     * This method MUST be compatible with set_error_handler.
453
     *
454
     * @param int         $level
455
     * @param string      $message
456
     * @param string|null $file
457
     * @param int|null    $line
458
     *
459
     * @return bool
460
     *
461
     * @throws ErrorException
462
     */
463
    public function handleError($level, $message, $file = null, $line = null)
464
    {
465
        if ($level & $this->system->getErrorReportingLevel()) {
466
            foreach ($this->silencedPatterns as $entry) {
467
                $pathMatches = (bool) preg_match($entry["pattern"], $file);
468
                $levelMatches = $level & $entry["levels"];
469
                if ($pathMatches && $levelMatches) {
470
                    // Ignore the error, abort handling
471
                    // See https://github.com/filp/whoops/issues/418
472
                    return true;
473
                }
474
            }
475
 
476
            // XXX we pass $level for the "code" param only for BC reasons.
477
            // see https://github.com/filp/whoops/issues/267
478
            $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
479
            if ($this->canThrowExceptions) {
480
                throw $exception;
481
            } else {
482
                $this->handleException($exception);
483
            }
484
            // Do not propagate errors which were already handled by Whoops.
485
            return true;
486
        }
487
 
488
        // Propagate error to the next handler, allows error_get_last() to
489
        // work on silenced errors.
490
        return false;
491
    }
492
 
493
    /**
494
     * Special case to deal with Fatal errors and the like.
495
     *
496
     * @return void
497
     */
498
    public function handleShutdown()
499
    {
500
        // If we reached this step, we are in shutdown handler.
501
        // An exception thrown in a shutdown handler will not be propagated
502
        // to the exception handler. Pass that information along.
503
        $this->canThrowExceptions = false;
504
 
505
        $error = $this->system->getLastError();
506
        if ($error && Misc::isLevelFatal($error['type'])) {
507
            // If there was a fatal error,
508
            // it was not handled in handleError yet.
509
            $this->allowQuit = false;
510
            $this->handleError(
511
                $error['type'],
512
                $error['message'],
513
                $error['file'],
514
                $error['line']
515
            );
516
        }
517
    }
518
 
688 lars 519
 
148 lars 520
    /**
688 lars 521
     * @param InspectorFactoryInterface $factory
522
     *
523
     * @return void
524
     */
525
    public function setInspectorFactory(InspectorFactoryInterface $factory)
526
    {
527
        $this->inspectorFactory = $factory;
528
    }
529
 
530
    public function addFrameFilter($filterCallback)
531
    {
532
        if (!is_callable($filterCallback)) {
533
            throw new \InvalidArgumentException(sprintf(
534
                "A frame filter must be of type callable, %s type given.",
535
                gettype($filterCallback)
536
            ));
537
        }
538
 
539
        $this->frameFilters[] = $filterCallback;
540
        return $this;
541
    }
542
 
543
    /**
148 lars 544
     * @param Throwable $exception
545
     *
688 lars 546
     * @return InspectorInterface
148 lars 547
     */
548
    private function getInspector($exception)
549
    {
688 lars 550
        return $this->inspectorFactory->create($exception);
148 lars 551
    }
552
 
553
    /**
554
     * Resolves the giving handler.
555
     *
556
     * @param callable|HandlerInterface $handler
557
     *
558
     * @return HandlerInterface
559
     *
560
     * @throws InvalidArgumentException
561
     */
562
    private function resolveHandler($handler)
563
    {
564
        if (is_callable($handler)) {
565
            $handler = new CallbackHandler($handler);
566
        }
567
 
568
        if (!$handler instanceof HandlerInterface) {
569
            throw new InvalidArgumentException(
570
                "Handler must be a callable, or instance of "
571
                . "Whoops\\Handler\\HandlerInterface"
572
            );
573
        }
574
 
575
        return $handler;
576
    }
577
 
578
    /**
579
     * Echo something to the browser.
580
     *
581
     * @param string $output
582
     *
583
     * @return Run
584
     */
585
    private function writeToOutputNow($output)
586
    {
587
        if ($this->sendHttpCode() && Misc::canSendHeaders()) {
588
            $this->system->setHttpResponseCode(
589
                $this->sendHttpCode()
590
            );
591
        }
592
 
593
        echo $output;
594
 
595
        return $this;
596
    }
597
}