Completed
Pull Request — v1 (#422)
by
unknown
03:17
created

Run::handleException()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 61
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8.7868

Importance

Changes 8
Bugs 2 Features 0
Metric Value
c 8
b 2
f 0
dl 0
loc 61
ccs 20
cts 26
cp 0.7692
rs 7.0047
cc 8
eloc 22
nc 36
nop 1
crap 8.7868

How to fix   Long Method   

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;
8
9
use InvalidArgumentException;
10
use Whoops\Exception\ErrorException;
11
use Whoops\Exception\Inspector;
12
use Whoops\Handler\CallbackHandler;
13
use Whoops\Handler\Handler;
14
use Whoops\Handler\HandlerInterface;
15
16
class Run
17
{
18
    const EXCEPTION_HANDLER = "handleException";
19
    const ERROR_HANDLER     = "handleError";
20
    const SHUTDOWN_HANDLER  = "handleShutdown";
21
22
    protected $isRegistered;
23
    protected $allowQuit       = true;
24
    protected $sendOutput      = true;
25
26
    /**
27
     * @var integer|false
28
     */
29
    protected $sendHttpCode    = 500;
30
31
    /**
32
     * @var HandlerInterface[]
33
     */
34
    protected $handlerStack = array();
35
36
    protected $silencedPatterns = array();
37
38
    /**
39
     * Pushes a handler to the end of the stack
40
     *
41
     * @throws InvalidArgumentException  If argument is not callable or instance of HandlerInterface
42
     * @param  Callable|HandlerInterface $handler
43
     * @return Run
44
     */
45 9
    public function pushHandler($handler)
46
    {
47 9
        if (is_callable($handler)) {
48 1
            $handler = new CallbackHandler($handler);
49 1
        }
50
51 9
        if (!$handler instanceof HandlerInterface) {
52 1
            throw new InvalidArgumentException(
53 1
                  "Argument to " . __METHOD__ . " must be a callable, or instance of"
54 1
                . "Whoops\\Handler\\HandlerInterface"
55 1
            );
56
        }
57
58 8
        $this->handlerStack[] = $handler;
59 8
        return $this;
60
    }
61
62
    /**
63
     * Removes the last handler in the stack and returns it.
64
     * Returns null if there"s nothing else to pop.
65
     * @return null|HandlerInterface
66
     */
67 1
    public function popHandler()
68
    {
69 1
        return array_pop($this->handlerStack);
70
    }
71
72
    /**
73
     * Returns an array with all handlers, in the
74
     * order they were added to the stack.
75
     * @return array
76
     */
77 2
    public function getHandlers()
78
    {
79 2
        return $this->handlerStack;
80
    }
81
82
    /**
83
     * Clears all handlers in the handlerStack, including
84
     * the default PrettyPage handler.
85
     * @return Run
86
     */
87 1
    public function clearHandlers()
88
    {
89 1
        $this->handlerStack = array();
90 1
        return $this;
91
    }
92
93
    /**
94
     * @param  \Exception | \Throwable $exception
95
     * @return Inspector
96
     */
97 2
    protected function getInspector($exception)
98
    {
99 2
        return new Inspector($exception);
100
    }
101
102
    /**
103
     * Registers this instance as an error handler.
104
     * @return Run
105
     */
106 5
    public function register()
107
    {
108 5
        if (!$this->isRegistered) {
109
            // Workaround PHP bug 42098
110
            // https://bugs.php.net/bug.php?id=42098
111 5
            class_exists("\\Whoops\\Exception\\ErrorException");
112 5
            class_exists("\\Whoops\\Exception\\FrameCollection");
113 5
            class_exists("\\Whoops\\Exception\\Frame");
114 5
            class_exists("\\Whoops\\Exception\\Inspector");
115
116 5
            set_error_handler(array($this, self::ERROR_HANDLER));
117 5
            set_exception_handler(array($this, self::EXCEPTION_HANDLER));
118 5
            register_shutdown_function(array($this, self::SHUTDOWN_HANDLER));
119
120 5
            $this->isRegistered = true;
121 5
        }
122
123 5
        return $this;
124
    }
125
126
    /**
127
     * Unregisters all handlers registered by this Whoops\Run instance
128
     * @return Run
129
     */
130 1
    public function unregister()
131
    {
132 1
        if ($this->isRegistered) {
133 1
            restore_exception_handler();
134 1
            restore_error_handler();
135
136 1
            $this->isRegistered = false;
137 1
        }
138
139 1
        return $this;
140
    }
141
142
    /**
143
     * Should Whoops allow Handlers to force the script to quit?
144
     * @param  bool|int $exit
145
     * @return bool
146
     */
147 5
    public function allowQuit($exit = null)
148
    {
149 5
        if (func_num_args() == 0) {
150 2
            return $this->allowQuit;
151
        }
152
153 5
        return $this->allowQuit = (bool) $exit;
154
    }
155
156
    /**
157
     * Silence particular errors in particular files
158
     * @param  array|string $patterns List or a single regex pattern to match
159
     * @param  int          $levels   Defaults to E_STRICT | E_DEPRECATED
160
     * @return \Whoops\Run
161
     */
162 1
    public function silenceErrorsInPaths($patterns, $levels = 10240)
163
    {
164 1
        $this->silencedPatterns = array_merge(
165 1
            $this->silencedPatterns,
166 1
            array_map(
167 1
                function ($pattern) use ($levels) {
168
                    return array(
169 1
                        "pattern" => $pattern,
170 1
                        "levels" => $levels,
171 1
                    );
172 1
                },
173
                (array) $patterns
174 1
            )
175 1
        );
176 1
        return $this;
177
    }
178
179
    /*
180
     * Should Whoops send HTTP error code to the browser if possible?
181
     * Whoops will by default send HTTP code 500, but you may wish to
182
     * use 502, 503, or another 5xx family code.
183
     *
184
     * @param bool|int $code
185
     * @return int|false
186
     */
187 4
    public function sendHttpCode($code = null)
188
    {
189 4
        if (func_num_args() == 0) {
190 3
            return $this->sendHttpCode;
191
        }
192
193 2
        if (!$code) {
194
            return $this->sendHttpCode = false;
195
        }
196
197 2
        if ($code === true) {
198 1
            $code = 500;
199 1
        }
200
201 2
        if ($code < 400 || 600 <= $code) {
202 1
            throw new InvalidArgumentException(
203 1
                 "Invalid status code '$code', must be 4xx or 5xx"
204 1
            );
205
        }
206
207 1
        return $this->sendHttpCode = $code;
208
    }
209
210
    /**
211
     * Should Whoops push output directly to the client?
212
     * If this is false, output will be returned by handleException
213
     * @param  bool|int $send
214
     * @return bool
215
     */
216 4
    public function writeToOutput($send = null)
217
    {
218 4
        if (func_num_args() == 0) {
219 4
            return $this->sendOutput;
220
        }
221
222 1
        return $this->sendOutput = (bool) $send;
223
    }
224
225
    /**
226
     * Handles an exception, ultimately generating a Whoops error
227
     * page.
228
     *
229
     * @param  \Exception | \Throwable $exception
230
     * @return string    Output generated by handlers
231
     */
232 6
    public function handleException($exception)
233
    {
234
        // Walk the registered handlers in the reverse order
235
        // they were registered, and pass off the exception
236 6
        $inspector = $this->getInspector($exception);
237
238
        // Capture output produced while handling the exception,
239
        // we might want to send it straight away to the client,
240
        // or return it silently.
241 6
        ob_start();
242
243
        // Just in case there are no handlers:
244 6
        $handlerResponse = null;
245
246 6
        foreach (array_reverse($this->handlerStack) as $handler) {
247 6
            $handler->setRun($this);
248 6
            $handler->setInspector($inspector);
249 6
            $handler->setException($exception);
250
251
            // The HandlerInterface does not require an Exception passed to handle()
252
            // and neither of our bundled handlers use it.
253
            // However, 3rd party handlers may have already relied on this parameter,
254
            // and removing it would be possibly breaking for users.
255 6
            $handlerResponse = $handler->handle($exception);
256
257 6
            if (in_array($handlerResponse, array(Handler::LAST_HANDLER, Handler::QUIT))) {
258
                // The Handler has handled the exception in some way, and
259
                // wishes to quit execution (Handler::QUIT), or skip any
260
                // other handlers (Handler::LAST_HANDLER). If $this->allowQuit
261
                // is false, Handler::QUIT behaves like Handler::LAST_HANDLER
262 3
                break;
263
            }
264 6
        }
265
266 6
        $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
267
268 6
        $output = ob_get_clean();
269
270
        // If we're allowed to, send output generated by handlers directly
271
        // to the output, otherwise, and if the script doesn't quit, return
272
        // it so that it may be used by the caller
273 6
        if ($this->writeToOutput()) {
274
            // @todo Might be able to clean this up a bit better
275
            // If we're going to quit execution, cleanup all other output
276
            // buffers before sending our own output:
277 5
            if ($willQuit) {
278
                while (ob_get_level() > 0) {
279
                    ob_end_clean();
280
                }
281
            }
282
283 5
            $this->writeToOutputNow($output);
284 5
        }
285
286 6
        if ($willQuit) {
287
            flush(); // HHVM fix for https://github.com/facebook/hhvm/issues/4055
288
            exit(1);
289
        }
290
291 6
        return $output;
292
    }
293
294
    /**
295
     * Converts generic PHP errors to \ErrorException
296
     * instances, before passing them off to be handled.
297
     *
298
     * This method MUST be compatible with set_error_handler.
299
     *
300
     * @param int    $level
301
     * @param string $message
302
     * @param string $file
303
     * @param int    $line
304
     *
305
     * @return bool
306
     * @throws ErrorException
307
     */
308 5
    public function handleError($level, $message, $file = null, $line = null)
309
    {
310 5
        if ($level & error_reporting()) {
311 2
            foreach ($this->silencedPatterns as $entry) {
312
                $pathMatches = (bool) preg_match($entry["pattern"], $file);
313
                $levelMatches = $level & $entry["levels"];
314
                if ($pathMatches && $levelMatches) {
315
                    // Ignore the error, abort handling
316
                    return true;
317
                }
318 2
            }
319
320
            // XXX we pass $level for the "code" param only for BC reasons.
321
            // see https://github.com/filp/whoops/issues/267
322 2
            $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
323 2
            if ($this->canThrowExceptions) {
324 2
                throw $exception;
325
            } else {
326
                $this->handleException($exception);
327
            }
328
            // Do not propagate errors which were already handled by Whoops.
329
            return true;
330
        }
331
332
        // Propagate error to the next handler, allows error_get_last() to
333
        // work on silenced errors.
334 3
        return false;
335
    }
336
337
    /**
338
     * Special case to deal with Fatal errors and the like.
339
     */
340
    public function handleShutdown()
341
    {
342
        // If we reached this step, we are in shutdown handler.
343
        // An exception thrown in a shutdown handler will not be propagated
344
        // to the exception handler. Pass that information along.
345
        $this->canThrowExceptions = false;
346
347
        $error = error_get_last();
348
        if ($error && $this->isLevelFatal($error['type'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            // If there was a fatal error,
350
            // it was not handled in handleError yet.
351
            $this->handleError(
352
                $error['type'],
353
                $error['message'],
354
                $error['file'],
355
                $error['line']
356
            );
357
        }
358
    }
359
360
    /**
361
     * In certain scenarios, like in shutdown handler, we can not throw exceptions
362
     * @var bool
363
     */
364
    private $canThrowExceptions = true;
365
366
    /**
367
     * Echo something to the browser
368
     * @param  string $output
369
     * @return $this
370
     */
371 2
    private function writeToOutputNow($output)
372
    {
373 2
        if ($this->sendHttpCode() && \Whoops\Util\Misc::canSendHeaders()) {
374
            $httpCode   = $this->sendHttpCode();
375
376
            if (function_exists('http_response_code')) {
377
                http_response_code($httpCode);
378
            } else {
379
                // http_response_code is added in 5.4.
380
                // For compatibility with 5.3 we use the third argument in header call
381
                // First argument must be a real header.
382
                // If it is empty, PHP will ignore the third argument.
383
                // If it is invalid, such as a single space, Apache will handle it well,
384
                // but the PHP development server will hang.
385
                // Setting a full status line would require us to hardcode
386
                // string values for all different status code, and detect the protocol.
387
                // which is an extra error-prone complexity.
388
                header('X-Ignore-This: 1', true, $httpCode);
389
            }
390
        }
391
392 2
        echo $output;
393
394 2
        return $this;
395
    }
396
397
    private static function isLevelFatal($level)
398
    {
399
        $errors = E_ERROR;
400
        $errors |= E_PARSE;
401
        $errors |= E_CORE_ERROR;
402
        $errors |= E_CORE_WARNING;
403
        $errors |= E_COMPILE_ERROR;
404
        $errors |= E_COMPILE_WARNING;
405
        return ($level & $errors) > 0;
406
    }
407
}
408