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        'new_password',
34        'new_password2',
35        'new_pw',
36        'new_pw2',
37        '_token',
38    ];
39
40    public function __construct(protected TwigLoader $loader)
41    {
42    }
43
44    /**
45     * Handles any error messages / http exceptions / validation errors
46     *
47     * Should be added at the beginning
48     */
49    public function process(
50        ServerRequestInterface $request,
51        RequestHandlerInterface $handler
52    ): ResponseInterface {
53        // Handle response
54        try {
55            $response = $handler->handle($request);
56        } catch (HttpException $e) {
57            $response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
58        } catch (ValidationException $e) {
59            $response = $this->redirectBack();
60            $response->with(
61                'messages.' . NotificationType::ERROR->value,
62                ['validation' => $e->getValidator()->getErrors()]
63            );
64
65            if ($request instanceof Request) {
66                $response->withInput(Arr::except($request->request->all(), $this->formIgnore));
67            }
68        } catch (ModelNotFoundException) {
69            $response = $this->createResponse('', 404);
70        }
71
72        $statusCode = $response->getStatusCode();
73        $contentType = $response->getHeader('content-type');
74        $contentType = array_shift($contentType);
75        if (!$contentType && str_contains($response->getBody()?->getContents() ?? '', '<html')) {
76            $contentType = 'text/html';
77        }
78
79        // Handle response based on status
80        if (
81            $statusCode < 400
82            || !$response instanceof Response
83            || !empty($contentType)
84        ) {
85            return $response;
86        }
87
88        $view = $this->selectView($statusCode);
89
90        return $response->withView(
91            $this->viewPrefix . $view,
92            [
93                'status'  => $statusCode,
94                'content' => $response->getContent(),
95            ],
96            $statusCode,
97            $response->getHeaders()
98        );
99    }
100
101    /**
102     * Select a view based on the given status code
103     */
104    protected function selectView(int $statusCode): string
105    {
106        $hundreds = intdiv($statusCode, 100);
107
108        $viewsList = [$statusCode, $hundreds, $hundreds * 100];
109        foreach ($viewsList as $view) {
110            if ($this->loader->exists($this->viewPrefix . $view)) {
111                return (string) $view;
112            }
113        }
114
115        return 'default';
116    }
117
118    /**
119     * Create a new response
120     *
121     * @codeCoverageIgnore
122     */
123    protected function createResponse(string $content = '', int $status = 200, array $headers = []): ResponseInterface
124    {
125        return response($content, $status, $headers);
126    }
127
128    /**
129     * Create a redirect back response
130     */
131    protected function redirectBack(): Response
132    {
133        return back();
134    }
135}