Passed
Push — master ( c588fc...30392d )
by Gerrit
02:01
created

applyResponseDataForException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 6
cts 6
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
1
<?php
2
/**
3
 * Copyright (C) 2018 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 *
8
 * @license GPL-3.0
9
 *
10
 * @author Gerrit Addiks <[email protected]>
11
 */
12
13
namespace Addiks\SymfonyGenerics\Controllers;
14
15
use Addiks\SymfonyGenerics\Controllers\ControllerHelperInterface;
16
use Webmozart\Assert\Assert;
17
use Exception;
18
use Throwable;
19
use Symfony\Component\HttpFoundation\Response;
20
use Addiks\SymfonyGenerics\Services\ArgumentCompilerInterface;
21
use Symfony\Component\HttpFoundation\Request;
22
use ReflectionMethod;
23
24
final class GenericExceptionResponseController
25
{
26
27
    /**
28
     * @var ControllerHelperInterface
29
     */
30
    private $controllerHelper;
31
32
    /**
33
     * @var ArgumentCompilerInterface
34
     */
35
    private $argumentBuilder;
36
37
    /**
38
     * @var object
39
     */
40
    private $innerController;
41
42
    /**
43
     * @var string
44
     */
45
    private $innerControllerMethod;
46
47
    /**
48
     * @var array
49
     */
50
    private $innerControllerArgumentsConfiguration;
51
52
    /**
53
     * @var string|null
54
     */
55
    private $successResponse;
56
57
    /**
58
     * @var int
59
     */
60
    private $successResponseCode;
61
62
    /**
63
     * @var string
64
     */
65
    private $successFlashMessage;
66
67
    /**
68
     * @var array<string, array<string, mixed>>
69
     */
70
    private $exceptionResponses = array();
71
72 16
    public function __construct(
73
        ControllerHelperInterface $controllerHelper,
74
        ArgumentCompilerInterface $argumentBuilder,
75
        array $options
76
    ) {
77
        /** @var int $defaultResponseCode */
78 16
        $defaultResponseCode = 200;
79
80
        /** @var array<string, mixed> $defaults */
81
        $defaults = array(
82 16
            'inner-controller-method' => '__invoke',
83
            'arguments' => [],
84
            'exception-responses' => [],
85
            'success-response' => null,
86 16
            'success-response-code' => $defaultResponseCode,
87 16
            'success-flash-message' => "",
88
        );
89
90
        /** @var mixed $options */
91 16
        $options = array_merge($defaults, $options);
92
93 16
        Assert::null($this->controllerHelper);
94 16
        Assert::true(is_object($options['inner-controller']));
95 15
        Assert::isArray($options['arguments']);
96
97 14
        $this->controllerHelper = $controllerHelper;
98 14
        $this->argumentBuilder = $argumentBuilder;
99 14
        $this->innerController = $options['inner-controller'];
100 14
        $this->innerControllerMethod = $options['inner-controller-method'];
101 14
        $this->innerControllerArgumentsConfiguration = $options['arguments'];
102 14
        $this->successResponse = $options['success-response'];
103 14
        $this->successResponseCode = $options['success-response-code'];
104 14
        $this->successFlashMessage = $options['success-flash-message'];
105
106 14
        foreach ($options['exception-responses'] as $exceptionClass => $responseData) {
107
            /** @var array<string, mixed> $responseData */
108
109
            /** @var string $responseCode */
110 6
            $responseCode = '500';
111
112 6
            if (isset($responseData['redirect-route'])) {
113 2
                $responseCode = '303';
114
            }
115
116 6
            $responseData = array_merge([
117 6
                'exception-class' => is_int($exceptionClass) ?null :$exceptionClass,
118 6
                'message' => '', # empty => exception message used
119 6
                'code' => $responseCode,
120 6
                'flash-type' => '', # empty => no message triggered
121 6
                'flash-message' => '%s', # empty => exception message used
122
                'redirect-route' => null,
123
                'redirect-route-parameters' => [],
124 6
                'filter' => '',
125 6
            ], $responseData);
126
127 6
            Assert::true(
128 6
                is_a($responseData['exception-class'], Throwable::class, true) ||
129 6
                empty($responseData['exception-class'])
130
            );
131
132 5
            $this->exceptionResponses[] = $responseData;
133
        }
134 13
    }
135
136 2 View Code Duplication
    public function __invoke(): Response
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
    {
138
        /** @var Request $request */
139 2
        $request = $this->controllerHelper->getCurrentRequest();
140
141 2
        Assert::isInstanceOf($request, Request::class, "Cannot use controller outside of request-scope!");
142
143 1
        return $this->executeInnerControllerSafely($request);
144
    }
145
146 11
    public function executeInnerControllerSafely(Request $request): Response
147
    {
148
        /** @var Response|null $response */
149 11
        $response = null;
150
151
        try {
152 11
            $response = $this->executeInnerControllerUnsafely($request);
153
154 7
        } catch (Throwable $exception) {
155 7
            $this->controllerHelper->handleException($exception);
156
157 7
            foreach ($this->exceptionResponses as $responseData) {
158
                /** @var array<string, mixed> $responseData */
159
160 5
                if ($this->shouldApplyResponseDataForException($exception, $responseData)) {
161 5
                    $response = $this->applyResponseDataForException($request, $exception, $responseData);
162 5
                    break;
163
                }
164
            }
165
166 7
            if (is_null($response)) {
167 2
                throw $exception;
168
            }
169
        }
170
171 9
        return $response;
172
    }
173
174 11
    private function executeInnerControllerUnsafely(Request $request): Response
175
    {
176 11
        $methodReflection = new ReflectionMethod($this->innerController, $this->innerControllerMethod);
177
178
        /** @var array<int, mixed> $arguments */
179 11
        $arguments = $this->argumentBuilder->buildCallArguments(
180 11
            $methodReflection,
181 11
            $this->innerControllerArgumentsConfiguration,
182 11
            $request
183
        );
184
185
        /** @var Response $innerResponse */
186 11
        $innerResponse = call_user_func_array([$this->innerController, $this->innerControllerMethod], $arguments);
187
188 5
        Assert::isInstanceOf($innerResponse, Response::class, "Controller did not return an Response object!");
189
190 4
        if (!empty($this->successFlashMessage)) {
191 1
            $this->controllerHelper->addFlashMessage($this->successFlashMessage, "success");
192
        }
193
194 4
        if (!is_null($this->successResponse)) {
195 2
            $response = new Response($this->successResponse, $this->successResponseCode);
196
197
        } else {
198 2
            $response = $innerResponse;
199
        }
200
201 4
        return $response;
202
    }
203
204 5
    private function shouldApplyResponseDataForException(Throwable $exception, array $responseData): bool
205
    {
206 5
        if (!empty($responseData['exception-class']) && !is_a($exception, $responseData['exception-class'])) {
207 1
            return false;
208
        }
209
210 5
        if (!empty($responseData['filter'])) {
211 1
            if (!preg_match("/" . $responseData['filter'] . "/is", $exception->getMessage())) {
212 1
                return false;
213
            }
214
        }
215
216 5
        return true;
217
    }
218
219 2
    private function createRedirectResponseForException(Request $request, array $responseData): Response
220
    {
221
        /** @var array $redirectRouteParameters */
222 2
        $redirectRouteParameters = array_merge(
223 2
            $request->attributes->get('_route_params'),
224 2
            $this->argumentBuilder->buildArguments(
225 2
                $responseData['redirect-route-parameters'],
226 2
                $request
227
            )
228
        );
229
230 2
        return $this->controllerHelper->redirectToRoute(
231 2
            $responseData['redirect-route'],
232 2
            $redirectRouteParameters,
233 2
            $responseData['code']
234
        );
235
    }
236
237 5
    private function applyResponseDataForException(Request $request, Throwable $exception, array $responseData): Response
238
    {
239 5
        $this->handleFlashMessageForException($exception, $responseData);
240
241 5
        if (!empty($responseData['redirect-route'])) {
242 2
            $response = $this->createRedirectResponseForException($request, $responseData);
243
244
        } else {
245 3
            $response = $this->createMessageResponseForException($exception, $responseData);
246
        }
247
248 5
        return $response;
249
    }
250
251 3
    private function createMessageResponseForException(Throwable $exception, array $responseData): Response
252
    {
253
        /** @var string $responseMessage */
254 3
        $responseMessage = $responseData['message'];
255
256 3
        if (empty($responseMessage)) {
257 3
            $responseMessage = $exception->getMessage();
258
        }
259
260 3
        return new Response($responseMessage, $responseData['code']);
261
    }
262
263 5
    private function handleFlashMessageForException(Throwable $exception, array $responseData): void
264
    {
265 5
        if (!empty($responseData['flash-type'])) {
266
            /** @var string $flashMessage */
267 3
            $flashMessage = sprintf($responseData['flash-message'], $exception->getMessage());
268
269 3
            $this->controllerHelper->addFlashMessage($flashMessage, $responseData['flash-type']);
270
        }
271 5
    }
272
273
}
274