Passed
Push — main ( 4bc471...1a87b8 )
by Peter
02:53
created

ExceptionRenderer   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 72
dl 0
loc 153
rs 10
c 2
b 0
f 0
wmc 13

7 Methods

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