Completed
Push — master ( 01eb59...230b2e )
by Oscar
02:45
created

ErrorHandler::errorHtml()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
rs 9.4285
cc 1
eloc 7
nc 1
nop 2
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr7Middlewares\Utils;
6
use Psr\Http\Message\ServerRequestInterface;
7
use Psr\Http\Message\ResponseInterface;
8
9
/**
10
 * Middleware to handle php errors and exceptions.
11
 */
12
class ErrorHandler
13
{
14
    const KEY = 'EXCEPTION';
15
16
    use Utils\CallableTrait;
17
    use Utils\AttributeTrait;
18
19
    /**
20
     * @var callable|string The handler used
21
     */
22
    private $handler;
23
24
    /**
25
     * @var bool Whether or not catch exceptions
26
     */
27
    private $catchExceptions = false;
28
29
    /**
30
     * Returns the exception throwed.
31
     *
32
     * @param ServerRequestInterface $request
33
     *
34
     * @return \Exception|null
35
     */
36
    public static function getException(ServerRequestInterface $request)
37
    {
38
        return self::getAttribute($request, self::KEY);
39
    }
40
41
    /**
42
     * Constructor.
43
     *
44
     * @param callable|string|null $handler
45
     */
46
    public function __construct($handler = null)
47
    {
48
        $this->handler = $handler;
49
    }
50
51
    /**
52
     * Configure the catchExceptions.
53
     *
54
     * @param bool $catch
55
     *
56
     * @return self
57
     */
58
    public function catchExceptions($catch = true)
59
    {
60
        $this->catchExceptions = (boolean) $catch;
61
62
        return $this;
63
    }
64
65
    /**
66
     * Execute the middleware.
67
     *
68
     * @param ServerRequestInterface $request
69
     * @param ResponseInterface      $response
70
     * @param callable               $next
71
     *
72
     * @return ResponseInterface
73
     */
74
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
75
    {
76
        ob_start();
77
        $level = ob_get_level();
78
79
        try {
80
            $response = $next($request, $response);
81
        } catch (\Throwable $exception) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
82
            if (!$this->catchExceptions) {
83
                throw $exception;
84
            }
85
86
            $request = self::setAttribute($request, self::KEY, $exception);
87
            $response = $response->withStatus(500);
88
        } catch (\Exception $exception) {
89
            if (!$this->catchExceptions) {
90
                throw $exception;
91
            }
92
93
            $request = self::setAttribute($request, self::KEY, $exception);
94
            $response = $response->withStatus(500);
95
        } finally {
96
            Utils\Helpers::getOutput($level);
97
        }
98
99
        if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) {
100
            $callable = $this->handler ?: [$this, 'defaultHandler'];
101
102
            return $this->executeCallable($callable, $request, $response);
103
        }
104
105
        return $response;
106
    }
107
108
    /**
109
     * Default handler.
110
     * 
111
     * @param ServerRequestInterface $request
112
     * @param ResponseInterface      $response
113
     * 
114
     * @return string
115
     */
116
    private function defaultHandler(ServerRequestInterface $request, ResponseInterface $response)
117
    {
118
        $statusCode = $response->getStatusCode();
119
        $exception = self::getException($request);
120
        $message = $exception ? $exception->getMessage() : '';
121
122
        switch (Utils\Helpers::getMimeType($response)) {
123
            case 'text/plain':
124
            case 'text/css':
125
            case 'text/javascript':
126
                return self::errorText($statusCode, $message);
127
128
            case 'image/jpeg':
129
                return self::errorImage($statusCode, $message, 'imagejpeg');
130
131
            case 'image/gif':
132
                return self::errorImage($statusCode, $message, 'imagegif');
133
134
            case 'image/png':
135
                return self::errorImage($statusCode, $message, 'imagepng');
136
137
            case 'image/svg+xml':
138
                return self::errorSvg($statusCode, $message);
139
140
            case 'application/json':
141
                return self::errorJson($statusCode, $message);
142
143
            case 'text/xml':
144
                return self::errorXml($statusCode, $message);
145
146
            default:
147
                return self::errorHtml($statusCode, $message);
148
        }
149
    }
150
151
    /**
152
     * Print the error as plain text.
153
     * 
154
     * @param int    $statusCode
155
     * @param string $message
156
     * 
157
     * @return string
158
     */
159
    private static function errorText($statusCode, $message)
160
    {
161
        return sprintf("Error %s\n%s", $statusCode, $message);
162
    }
163
164
    /**
165
     * Print the error as svg image.
166
     * 
167
     * @param int    $statusCode
168
     * @param string $message
169
     * 
170
     * @return string
171
     */
172
    private static function errorSvg($statusCode, $message)
173
    {
174
        return <<<EOT
175
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="50" viewBox="0 0 200 50">
176
    <text x="20" y="30" font-family="sans-serif" title="{$message}">
177
        Error {$statusCode}
178
    </text>
179
</svg>
180
EOT;
181
    }
182
183
    /**
184
     * Print the error as html.
185
     * 
186
     * @param int    $statusCode
187
     * @param string $message
188
     * 
189
     * @return string
190
     */
191
    private static function errorHtml($statusCode, $message)
192
    {
193
        return <<<EOT
194
<!DOCTYPE html>
195
<html>
196
<head>
197
    <meta charset="utf-8">
198
    <title>Error {$statusCode}</title>
199
    <style>html{font-family: sans-serif;}</style>
200
    <meta name="viewport" content="width=device-width, initial-scale=1">
201
</head>
202
<body>
203
    <h1>Error {$statusCode}</h1>
204
    {$message}
205
</body>
206
</html>
207
EOT;
208
    }
209
210
    /**
211
     * Print the error as image.
212
     * 
213
     * @param int    $statusCode
214
     * @param string $message
215
     * @param string $output
216
     * 
217
     * @return string
218
     */
219
    private static function errorImage($statusCode, $message, $output)
220
    {
221
        $size = 200;
222
        $image = imagecreatetruecolor($size, $size);
223
        $textColor = imagecolorallocate($image, 255, 255, 255);
224
        imagestring($image, 5, 10, 10, "Error {$statusCode}", $textColor);
225
226
        foreach (str_split($message, intval($size / 10)) as $line => $text) {
227
            imagestring($image, 5, 10, ($line * 18) + 28, $text, $textColor);
228
        }
229
230
        ob_start();
231
        $output($image);
232
233
        return ob_get_clean();
234
    }
235
236
    /**
237
     * Print the error as json.
238
     * 
239
     * @param int    $statusCode
240
     * @param string $message
241
     * 
242
     * @return string
243
     */
244
    private static function errorJson($statusCode, $message)
245
    {
246
        $output = ['error' => $statusCode];
247
248
        if (!empty($message)) {
249
            $output['message'] = $message;
250
        }
251
252
        return json_encode($output);
253
    }
254
255
    /**
256
     * Print the error as xml.
257
     * 
258
     * @param int    $statusCode
259
     * @param string $message
260
     * 
261
     * @return string
262
     */
263
    private static function errorXml($statusCode, $message)
264
    {
265
        return <<<EOT
266
<?xml version="1.0" encoding="UTF-8"?>
267
<error>
268
    <code>{$statusCode}</code>
269
    <message>{$message}</message>
270
</error>
271
EOT;
272
    }
273
}
274