Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ErrorHandler
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
4 / 4
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
10
 selectView
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 createResponse
n/a
0 / 0
n/a
0 / 0
1
 redirectBack
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Middleware;
6
7use Engelsystem\Controllers\NotificationType;
8use Engelsystem\Http\Exceptions\HttpException;
9use Engelsystem\Http\Exceptions\ValidationException;
10use Engelsystem\Http\Request;
11use Engelsystem\Http\Response;
12use Illuminate\Database\Eloquent\ModelNotFoundException;
13use Illuminate\Support\Arr;
14use Psr\Http\Message\ResponseInterface;
15use Psr\Http\Message\ServerRequestInterface;
16use Psr\Http\Server\MiddlewareInterface;
17use Psr\Http\Server\RequestHandlerInterface;
18use Twig\Loader\LoaderInterface as TwigLoader;
19
20class ErrorHandler implements MiddlewareInterface
21{
22    protected string $viewPrefix = 'errors/';
23
24    /**
25     * A list of inputs that are not saved from input
26     *
27     * @var array<string>
28     */
29    protected array $formIgnore = [
30        'password',
31        'password_confirmation',
32        'password2',
33        'database_password',
34        'email_password',
35        'new_password',
36        'new_password2',
37        'new_pw',
38        'new_pw2',
39        'app_key',
40        '_token',
41    ];
42
43    public function __construct(protected TwigLoader $loader)
44    {
45    }
46
47    /**
48     * Handles any error messages / http exceptions / validation errors
49     *
50     * Should be added at the beginning
51     */
52    public function process(
53        ServerRequestInterface $request,
54        RequestHandlerInterface $handler
55    ): ResponseInterface {
56        // Handle response
57        try {
58            $response = $handler->handle($request);
59        } catch (HttpException $e) {
60            $response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
61        } catch (ValidationException $e) {
62            $response = $this->redirectBack();
63            $response->with(
64                'messages.' . NotificationType::ERROR->value,
65                ['validation' => $e->getValidator()->getErrors()]
66            );
67
68            if ($request instanceof Request) {
69                $response->withInput(Arr::except($request->request->all(), $this->formIgnore));
70            }
71        } catch (ModelNotFoundException) {
72            $response = $this->createResponse('', 404);
73        }
74
75        $statusCode = $response->getStatusCode();
76        $contentType = $response->getHeader('content-type');
77        $contentType = array_shift($contentType);
78        if (!$contentType && str_contains($response->getBody()?->getContents() ?? '', '<html')) {
79            $contentType = 'text/html';
80        }
81
82        // Handle response based on status
83        if (
84            $statusCode < 400
85            || !$response instanceof Response
86            || !empty($contentType)
87        ) {
88            return $response;
89        }
90
91        $view = $this->selectView($statusCode);
92
93        return $response->withView(
94            $this->viewPrefix . $view,
95            [
96                'status'  => $statusCode,
97                'content' => $response->getContent(),
98            ],
99            $statusCode,
100            $response->getHeaders()
101        );
102    }
103
104    /**
105     * Select a view based on the given status code
106     */
107    protected function selectView(int $statusCode): string
108    {
109        $hundreds = intdiv($statusCode, 100);
110
111        $viewsList = [$statusCode, $hundreds, $hundreds * 100];
112        foreach ($viewsList as $view) {
113            if ($this->loader->exists($this->viewPrefix . $view)) {
114                return (string) $view;
115            }
116        }
117
118        return 'default';
119    }
120
121    /**
122     * Create a new response
123     *
124     * @codeCoverageIgnore
125     */
126    protected function createResponse(string $content = '', int $status = 200, array $headers = []): ResponseInterface
127    {
128        return response($content, $status, $headers);
129    }
130
131    /**
132     * Create a redirect back response
133     */
134    protected function redirectBack(): Response
135    {
136        return back();
137    }
138}