DefaultHttpErrorHandler::handle()   C
last analyzed

Complexity

Conditions 8
Paths 15

Size

Total Lines 37
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 26
nc 15
nop 2
dl 0
loc 37
ccs 0
cts 34
cp 0
crap 72
rs 5.3846
c 0
b 0
f 0
1
<?php
2
namespace Wandu\Foundation\WebApp;
3
4
use Psr\Http\Message\ServerRequestInterface;
5
use Psr\Log\LoggerInterface;
6
use Wandu\Config\Contracts\Config;
7
use Wandu\Foundation\WebApp\Contracts\HttpErrorHandler;
8
use Wandu\Http\Exception\HttpException;
9
use Wandu\Http\Psr\Stream\StringStream;
10
use Wandu\Router\Exception\MethodNotAllowedException as RouteMethodException;
11
use Wandu\Router\Exception\RouteNotFoundException;
12
use Wandu\Validator\Exception\InvalidValueException;
13
use Whoops\Handler\JsonResponseHandler;
14
use Whoops\Handler\PlainTextHandler;
15
use Whoops\Handler\PrettyPageHandler;
16
use Whoops\Run;
17
use function Wandu\Http\Response\create;
18
use function Wandu\Http\Response\json;
19
20
class DefaultHttpErrorHandler implements HttpErrorHandler
21
{
22
    /** @var \Psr\Log\LoggerInterface */
23
    protected $logger;
24
    
25
    /** @var \Wandu\Config\Contracts\Config */
26
    protected $config;
27
28
    /** @var array */
29
    protected $types = [
30
        /* ... */
31
    ];
32
    
33
    /** @var array */
34
    protected $statusCodes = [
35
        InvalidValueException::class => 400,
36
    ];
37
38
    /**
39
     * @param \Wandu\Config\Contracts\Config $config
40
     * @param \Psr\Log\LoggerInterface $logger
41
     */
42
    public function __construct(Config $config, LoggerInterface $logger = null)
43
    {
44
        $this->config = $config;
45
        $this->logger = $logger;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function handle(ServerRequestInterface $request, $exception)
52
    {
53
        if ($exception instanceof HttpException) {
54
            if ($this->logger) {
55
                $this->logger->notice($this->prettifyRequest($request));
56
                $this->logger->notice($exception);
57
            }
58
            return $exception;
59
        }
60
        if ($this->config->get('server.debug', true)) {
61
            $whoops = $this->getWhoops($request);
62
            return create(new StringStream($whoops->handleException($exception)), 500);
0 ignored issues
show
Security Bug introduced by
It seems like $whoops->handleException($exception) targeting Whoops\Run::handleException() can also be of type false; however, Wandu\Http\Psr\Stream\StringStream::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
63
        }
64
65
        $statusCode = 500;
66
        $reasonPhrase = 'Internal Server Error';
67
68
        if ($exception instanceof RouteNotFoundException) {
69
            $statusCode = 404;
70
            $reasonPhrase = "Not Found";
71
        } elseif ($exception instanceof RouteMethodException) {
72
            $statusCode = 405;
73
            $reasonPhrase = 'Method Not Allowed';
74
        }
75
        if ($this->logger) {
76
            $this->logger->error($this->prettifyRequest($request));
77
            $this->logger->error($exception);
78
        }
79
80
        if ($this->isAjax($request)) {
81
            return json([
82
                'status' => $statusCode,
83
                'reason' => $reasonPhrase,
84
            ], $statusCode);
85
        }
86
        return create(new StringStream("{$statusCode} {$reasonPhrase}"), $statusCode);
87
    }
88
89
    /**
90
     * @param \Psr\Http\Message\ServerRequestInterface $request
91
     * @return bool
92
     */
93
    protected function isAjax(ServerRequestInterface $request)
94
    {
95
        return $request->hasHeader('x-requested-with') &&
96
            $request->getHeaderLine('x-requested-with') === 'XMLHttpRequest';
97
    }
98
99
    /**
100
     * @param \Psr\Http\Message\ServerRequestInterface $request
101
     * @return string
102
     */
103
    protected function prettifyRequest(ServerRequestInterface $request)
104
    {
105
        $contents = "{$request->getMethod()} : {$request->getUri()->__toString()}\n";
106
        $contents .= "HEADERS\n";
107
        foreach ($request->getHeaders() as $name => $value) {
108
            $contents .= "    {$name} : {$request->getHeaderLine($name)}\n";
109
        }
110
        if ($body = $request->getBody()) {
111
            $contents .= "BODY\n";
112
            $contents .= "\"{$body->__toString()}\"\n";
113
        }
114
        return $contents;
115
    }
116
    
117
    protected function getWhoops(ServerRequestInterface $request)
118
    {
119
        $whoops = new Run();
120
121
        switch ($this->getAcceptType($request)) {
122
            case 'html':
123
                $whoops->pushHandler(new PrettyPageHandler());
0 ignored issues
show
Documentation introduced by
new \Whoops\Handler\PrettyPageHandler() is of type object<Whoops\Handler\PrettyPageHandler>, but the function expects a callable.

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...
124
                break;
125
            case 'json':
126
                $whoops->pushHandler(new JsonResponseHandler());
0 ignored issues
show
Documentation introduced by
new \Whoops\Handler\JsonResponseHandler() is of type object<Whoops\Handler\JsonResponseHandler>, but the function expects a callable.

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...
127
                break;
128
            default:
129
                $whoops->pushHandler(new PlainTextHandler());
0 ignored issues
show
Documentation introduced by
new \Whoops\Handler\PlainTextHandler() is of type object<Whoops\Handler\PlainTextHandler>, but the function expects a callable.

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...
130
        }
131
132
        $whoops->allowQuit(false);
133
        $whoops->writeToOutput(false);
134
        $whoops->sendHttpCode(false);
135
136
        return $whoops;
137
    }
138
139
    /**
140
     * @ref github.com/oscarotero/psr7-middlewares/blob/master/src/Middleware/FormatNegotiator.php
141
     *
142
     * @param \Psr\Http\Message\ServerRequestInterface $request
143
     * @return string
144
     */
145
    protected function getAcceptType(ServerRequestInterface $request)
146
    {
147
        $accept = $request->getHeaderLine('Accept');
148
        if (
149
            strpos($accept, 'text/html') !== false ||
150
            strpos($accept, 'application/xhtml+xml') !== false
151
        ) {
152
            return 'html';
153
        }
154
        if (
155
            strpos($accept, 'application/json') !== false ||
156
            strpos($accept, 'text/json') !== false ||
157
            strpos($accept, 'application/x-json') !== false
158
        ) {
159
            return 'json';
160
        }
161
        return 'text';
162
    }
163
}
164