Passed
Push — html ( fbdc06...58a6c6 )
by Peter
03:14
created

ExceptionRenderer::header()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Debug\Exceptions\Handlers\Whoops;
6
7
use Exception;
8
use Opulence\Framework\Debug\Exceptions\Handlers\Http;
9
use Opulence\Http\HttpException;
10
use Throwable;
11
use Whoops\RunInterface;
12
13
/**
14
 * @SuppressWarnings(PHPMD)
15
 */
16
class ExceptionRenderer extends Http\ExceptionRenderer implements Http\IExceptionRenderer
17
{
18
    public const HTML = <<< EOF
19
        <!DOCTYPE html>
20
        <html>
21
            <head>
22
                <meta name="viewport" content="initial-scale=1"/>
23
                <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
24
                    integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
25
                    crossorigin="anonymous">
26
            </head>
27
            <body>
28
                <main class="main">
29
                    <div class="container">
30
                        <div class="row">
31
                            <div class="col-sm">
32
                                <div class="alert alert-danger" role="alert">
33
                                    <h4 class="alert-heading">Exception: %s</h4>
34
                                    <p>%s</p>
35
                                    <hr>
36
                                    <p class="mb-0">
37
                                        <a href="#" class="btn btn-danger btn-lg active" role="button" aria-pressed="true"
38
                                        onclick="location.reload(); return false;">Try again</a>
39
                                        <a href="#" class="btn btn-primary btn-lg active" role="button" aria-pressed="true"
40
                                        onclick="window.history.back(); return false;">Back to previous page</a>
41
                                    </p>
42
                                </div>
43
                            </div>
44
                        </div>
45
                    </div>
46
                </main>
47
            </body>
48
        </html>
49
        EOF;
50
51
    protected array $headers = [];
52
53
    /** @var RunInterface */
54
    protected RunInterface $run;
55
56
    /**
57
     * @return bool
58
     */
59
    public function isInDevelopmentEnvironment(): bool
60
    {
61
        return $this->inDevelopmentEnvironment;
62
    }
63
64
    /**
65
     * @param bool $inDevelopmentEnvironment
66
     *
67
     * @return $this
68
     */
69
    public function setInDevelopmentEnvironment(bool $inDevelopmentEnvironment): self
70
    {
71
        $this->inDevelopmentEnvironment = $inDevelopmentEnvironment;
72
73
        return $this;
74
    }
75
76
    /**
77
     * WhoopsRenderer constructor.
78
     *
79
     * @param RunInterface $run
80
     * @param bool         $inDevelopmentEnvironment
81
     */
82
    public function __construct(RunInterface $run, bool $inDevelopmentEnvironment = false)
83
    {
84
        $this->run = $run;
85
86
        parent::__construct($inDevelopmentEnvironment);
87
    }
88
89
    /**
90
     * @return RunInterface
91
     */
92
    public function getRun(): RunInterface
93
    {
94
        return $this->run;
95
    }
96
97
    /**
98
     * Renders an exception
99
     *
100
     * @param Throwable|Exception $ex The thrown exception
101
     */
102
    public function render($ex): void
103
    {
104
        if (!$this->inDevelopmentEnvironment) {
105
            $this->run->writeToOutput(false);
106
107
            $this->run->unregister();
108
109
            $this->devRender($ex);
110
111
            return;
112
        }
113
114
        $this->run->handleException($ex);
115
    }
116
117
    /**
118
     * @param Exception $ex
119
     */
120
    public function devRender(Exception $ex): void
121
    {
122
        // Add support for HTTP library without having to necessarily depend on it
123
        $statusCode = 500;
124
        $headers    = [];
125
        if ($ex instanceof HttpException) {
126
            $statusCode = $ex->getStatusCode();
127
            $headers    = $ex->getHeaders();
128
        }
129
130
        switch ($this->getRequestFormat()) {
131
            case 'json':
132
                $headers['Content-Type'] = 'application/json';
133
                break;
134
            default:
135
                $content                 = sprintf(
0 ignored issues
show
Unused Code introduced by
The assignment to $content is dead and can be removed.
Loading history...
136
                    self::HTML,
137
                    get_class($ex),
138
                    $ex->getMessage()
139
                );
140
                $headers['Content-Type'] = 'text/html';
141
        }
142
143
        $this->devRenderHeaders($headers, $statusCode);
144
145
        // Always get the content, even if headers are sent, so that we can unit test this
146
        echo $ex->getMessage();
147
148
        // To prevent any potential output buffering, let's flush
149
        flush();
150
    }
151
152
    /**
153
     * @param array $headers
154
     * @param int   $statusCode
155
     */
156
    protected function devRenderHeaders(array $headers, int $statusCode): void
157
    {
158
        $this->header("HTTP/1.1 $statusCode", true, $statusCode);
159
160
        foreach ($headers as $name => $values) {
161
            $values = (array)$values;
162
163
            foreach ($values as $value) {
164
                $this->header("$name:$value", false);
165
            }
166
        }
167
    }
168
169
    /**
170
     * @param string   $header
171
     * @param bool     $replace
172
     * @param int|null $responseCode
173
     */
174
    protected function header(string $header, bool $replace = true, ?int $responseCode = null): void
175
    {
176
        $this->headers[] = [$header, $replace, $responseCode];
177
178
        if (!headers_sent()) {
179
            header($header, $replace, $responseCode);
0 ignored issues
show
Bug introduced by
It seems like $responseCode can also be of type null; however, parameter $response_code of header() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
            header($header, $replace, /** @scrutinizer ignore-type */ $responseCode);
Loading history...
180
        }
181
    }
182
183
    /**
184
     * @return array
185
     */
186
    public function getHeaders(): array
187
    {
188
        return $this->headers;
189
    }
190
}
191