Completed
Pull Request — master (#374)
by Guido
22:17
created

Run::handleException()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 63
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9.1158

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 63
ccs 20
cts 27
cp 0.7407
rs 6.8825
cc 8
eloc 22
nc 36
nop 1
crap 9.1158

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

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...
338
            }
339
            // Do not propagate errors which were already handled by Whoops.
340
            return true;
341
        }
342
343
        // Propagate error to the next handler, allows error_get_last() to
344
        // work on silenced errors.
345 3
        return false;
346
    }
347
348
    /**
349
     * Special case to deal with Fatal errors and the like.
350
     */
351
    public function handleShutdown()
352
    {
353
        // If we reached this step, we are in shutdown handler.
354
        // An exception thrown in a shutdown handler will not be propagated
355
        // to the exception handler. Pass that information along.
356
        $this->canThrowExceptions = false;
357
358
        $error = $this->system->getLastError();
359
        if ($error && Misc::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...
360
            // If there was a fatal error,
361
            // it was not handled in handleError yet.
362
            $this->handleError(
363
                $error['type'],
364
                $error['message'],
365
                $error['file'],
366
                $error['line']
367
            );
368
        }
369
    }
370
371
    /**
372
     * In certain scenarios, like in shutdown handler, we can not throw exceptions
373
     * @var bool
374
     */
375
    private $canThrowExceptions = true;
376
377
    /**
378
     * Echo something to the browser
379
     * @param  string $output
380
     * @return $this
381
     */
382 1
    private function writeToOutputNow($output)
383
    {
384 1
        if ($this->sendHttpCode() && \Whoops\Util\Misc::canSendHeaders()) {
385
            $this->system->setHttpResponseCode(
386
                $this->sendHttpCode()
0 ignored issues
show
Bug introduced by
It seems like $this->sendHttpCode() targeting Whoops\Run::sendHttpCode() can also be of type boolean; however, Whoops\Util\SystemFacade::setHttpResponseCode() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
387
            );
388
        }
389
390 1
        echo $output;
391
392 1
        return $this;
393
    }
394
}
395