Passed
Push — master ( 71359d...5fb051 )
by Greg
07:44
created

HandleExceptions::process()   B

Complexity

Conditions 9
Paths 50

Size

Total Lines 51
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 25
c 0
b 0
f 0
nc 50
nop 2
dl 0
loc 51
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\Middleware;
21
22
use Fig\Http\Message\RequestMethodInterface;
23
use Fig\Http\Message\StatusCodeInterface;
24
use Fisharebest\Localization\Locale\LocaleEnUs;
25
use Fisharebest\Webtrees\Http\ViewResponseTrait;
26
use Fisharebest\Webtrees\Log;
27
use Psr\Http\Message\ResponseInterface;
28
use Psr\Http\Message\ServerRequestInterface;
29
use Psr\Http\Server\MiddlewareInterface;
30
use Psr\Http\Server\RequestHandlerInterface;
31
use Symfony\Component\HttpKernel\Exception\HttpException;
32
use Throwable;
33
34
use function app;
35
use function dirname;
36
use function ob_end_clean;
37
use function ob_get_level;
38
use function response;
39
use function str_replace;
40
use function view;
41
42
use const PHP_EOL;
43
44
/**
45
 * Middleware to handle and render errors.
46
 */
47
class HandleExceptions implements MiddlewareInterface, StatusCodeInterface
48
{
49
    use ViewResponseTrait;
50
51
    /**
52
     * @param ServerRequestInterface  $request
53
     * @param RequestHandlerInterface $handler
54
     *
55
     * @return ResponseInterface
56
     * @throws Throwable
57
     */
58
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
59
    {
60
        try {
61
            return $handler->handle($request);
62
        } catch (HttpException $exception) {
63
            // The router added the tree attribute to the request, and we need it for the error response.
64
            $request = app(ServerRequestInterface::class) ?? $request;
65
66
            return $this->httpExceptionResponse($request, $exception);
67
        } catch (Throwable $exception) {
68
            // Exception thrown while buffering output?
69
            while (ob_get_level() > 0) {
70
                ob_end_clean();
71
            }
72
73
            // The Router middleware may have added a tree attribute to the request.
74
            // This might be usable in the error page.
75
            if (app()->has(ServerRequestInterface::class)) {
76
                $request = app(ServerRequestInterface::class) ?? $request;
77
            }
78
79
            // No locale set in the request?
80
            if ($request->getAttribute('locale') === null) {
81
                $request = $request->withAttribute('locale', new LocaleEnUs());
82
                app()->instance(ServerRequestInterface::class, $request);
83
            }
84
85
            // Show the exception in a standard webtrees page (if we can).
86
            try {
87
                return $this->unhandledExceptionResponse($request, $exception);
88
            } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
89
            }
90
91
            // Show the exception in a tree-less webtrees page (if we can).
92
            try {
93
                $request = $request->withAttribute('tree', null);
94
95
                return $this->unhandledExceptionResponse($request, $exception);
96
            } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
97
            }
98
99
            // Show the exception in an error page (if we can).
100
            try {
101
                $this->layout = 'layouts/error';
102
103
                return $this->unhandledExceptionResponse($request, $exception);
104
            } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
105
            }
106
107
            // Show a stack dump.
108
            return response((string) $exception, StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR);
109
        }
110
    }
111
112
    /**
113
     * @param ServerRequestInterface $request
114
     * @param HttpException          $exception
115
     *
116
     * @return ResponseInterface
117
     */
118
    private function httpExceptionResponse(ServerRequestInterface $request, HttpException $exception): ResponseInterface
119
    {
120
        $tree = $request->getAttribute('tree');
121
122
        if ($request->getHeaderLine('X-Requested-With') !== '') {
123
            $this->layout = 'layouts/ajax';
124
        }
125
126
        return $this->viewResponse('components/alert-danger', [
127
            'alert' => $exception->getMessage(),
128
            'title' => $exception->getMessage(),
129
            'tree'  => $tree,
130
        ], $exception->getStatusCode());
131
    }
132
133
    /**
134
     * @param ServerRequestInterface $request
135
     * @param Throwable              $exception
136
     *
137
     * @return ResponseInterface
138
     */
139
    private function unhandledExceptionResponse(ServerRequestInterface $request, Throwable $exception): ResponseInterface
140
    {
141
        $this->layout = 'layouts/default';
142
143
        // Create a stack dump for the exception
144
        $base_path = dirname(__DIR__, 3);
145
        $trace     = $exception->getMessage() . ' ' . $exception->getFile() . ':' . $exception->getLine() . PHP_EOL . $exception->getTraceAsString();
146
        $trace     = str_replace($base_path, '…', $trace);
147
148
        try {
149
            Log::addErrorLog($trace);
150
        } catch (Throwable $exception) {
151
            // Must have been a problem with the database.  Nothing we can do here.
152
        }
153
154
        if ($request->getHeaderLine('X-Requested-With') !== '') {
155
            // If this was a GET request, then we were probably fetching HTML to display, for
156
            // example a chart or tab.
157
            if ($request->getMethod() === RequestMethodInterface::METHOD_GET) {
158
                $status_code = StatusCodeInterface::STATUS_OK;
159
            } else {
160
                $status_code = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR;
161
            }
162
163
            return response(view('components/alert-danger', ['alert' => $trace]), $status_code);
164
        }
165
166
        return $this->viewResponse('errors/unhandled-exception', [
167
            'title' => 'Error',
168
            'error' => $trace,
169
            'request' => $request,
170
            'tree'  => null,
171
        ], StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR);
172
    }
173
}
174