Completed
Push — master ( 27d676...dee7dc )
by Gerrit
03:35
created

GenericExceptionResponseController::__invoke()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 9
loc 9
ccs 4
cts 4
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 ErrorException;
21
use Addiks\SymfonyGenerics\Services\ArgumentCompilerInterface;
22
use Symfony\Component\HttpFoundation\Request;
23
use ReflectionMethod;
24
25
final class GenericExceptionResponseController
26
{
27
28
    /**
29
     * @var ControllerHelperInterface
30
     */
31
    private $controllerHelper;
32
33
    /**
34
     * @var ArgumentCompilerInterface
35
     */
36
    private $argumentBuilder;
37
38
    /**
39
     * @var object
40
     */
41
    private $innerController;
42
43
    /**
44
     * @var string
45
     */
46
    private $innerControllerMethod;
47
48
    /**
49
     * @var array
50
     */
51
    private $innerControllerArgumentsConfiguration;
52
53
    /**
54
     * @var string|null
55
     */
56
    private $successResponse;
57
58
    /**
59
     * @var int
60
     */
61
    private $successResponseCode;
62
63
    /**
64
     * @var string|null
65
     */
66
    private $successFlashMessage;
67
68
    /**
69
     * @var array<string, array<string, mixed>>
70
     */
71
    private $exceptionResponses = array();
72
73
    /**
74
     * @param object $innerController
0 ignored issues
show
Bug introduced by
There is no parameter named $innerController. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
75
     */
76 15
    public function __construct(
77
        ControllerHelperInterface $controllerHelper,
78
        ArgumentCompilerInterface $argumentBuilder,
79
        array $options
80
    ) {
81
        /** @var int $defaultResponseCode */
82 15
        $defaultResponseCode = 200;
83
84
        /** @var array<string, mixed> $defaults */
85
        $defaults = array(
86 15
            'arguments' => [],
87
            'exception-responses' => [],
88
            'success-response' => null,
89 15
            'success-response-code' => $defaultResponseCode,
90
            'success-flash-message' => null,
91
        );
92
93
        /** @var mixed $options */
94 15
        $options = array_merge($defaults, $options);
95
96 15
        Assert::null($this->controllerHelper);
97 15
        Assert::true(is_object($options['inner-controller']));
98 14
        Assert::isArray($options['arguments']);
99
100 13
        $this->controllerHelper = $controllerHelper;
101 13
        $this->argumentBuilder = $argumentBuilder;
102 13
        $this->innerController = $options['inner-controller'];
103 13
        $this->innerControllerMethod = $options['inner-controller-method'];
104 13
        $this->innerControllerArgumentsConfiguration = $options['arguments'];
105 13
        $this->successResponse = $options['success-response'];
106 13
        $this->successResponseCode = $options['success-response-code'];
107 13
        $this->successFlashMessage = $options['success-flash-message'];
108
109 13
        foreach ($options['exception-responses'] as $exceptionClass => $responseData) {
110
            /** @var array<string, mixed> $responseData */
111
112 5
            Assert::true(
113 5
                is_subclass_of($exceptionClass, Exception::class) ||
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Exception::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
114 5
                is_subclass_of($exceptionClass, Throwable::class)
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Throwable::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
115
            );
116
117
            /** @var string $responseCode */
118 4
            $responseCode = '500';
119
120 4
            if (isset($responseData['redirect-route'])) {
121 2
                $responseCode = '301';
122
            }
123
124 4
            $responseData = array_merge([
125 4
                'message' => '', # empty => exception message used
126 4
                'code' => $responseCode,
127 4
                'flash-type' => '', # empty => no message triggered
128 4
                'flash-message' => '%s', # empty => exception message used
129
                'redirect-route' => null,
130
                'redirect-route-parameters' => [],
131 4
            ], $responseData);
132
133 4
            $this->exceptionResponses[$exceptionClass] = $responseData;
134
        }
135 12
    }
136
137 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...
138
    {
139
        /** @var Request $request */
140 2
        $request = $this->controllerHelper->getCurrentRequest();
141
142 2
        Assert::isInstanceOf($request, Request::class, "Cannot use controller outside of request-scope!");
143
144 1
        return $this->executeInnerControllerSafely($request);
145
    }
146
147 10
    public function executeInnerControllerSafely(Request $request): Response
148
    {
149
        /** @var Response|null $response */
150 10
        $response = null;
151
152
        /** @var Response|null $innerResponse */
153 10
        $innerResponse = null;
154
155
        try {
156
            /** @var array<int, mixed> $arguments */
157 10
            $arguments = array();# TODO
158
159 10
            $methodReflection = new ReflectionMethod($this->innerController, $this->innerControllerMethod);
160
161
            /** @var array<int, mixed> $arguments */
162 10
            $arguments = $this->argumentBuilder->buildCallArguments(
163 10
                $methodReflection,
164 10
                $this->innerControllerArgumentsConfiguration,
165 10
                $request
166
            );
167
168 10
            $innerResponse = call_user_func_array([$this->innerController, $this->innerControllerMethod], $arguments);
169
170 5
            Assert::isInstanceOf($innerResponse, Response::class, "Controller did not return an Response object!");
171
172 4
            if (!is_null($this->successFlashMessage)) {
173 1
                $this->controllerHelper->addFlashMessage($this->successFlashMessage, "success");
174
            }
175
176 4
            if (!is_null($this->successResponse)) {
177 2
                $response = new Response($this->successResponse, $this->successResponseCode);
178
179
            } else {
180 4
                $response = $innerResponse;
181
            }
182
183 6
        } catch (Exception $exception) {
184 5
            $this->controllerHelper->handleException($exception);
185
186 5
            foreach ($this->exceptionResponses as $exceptionClass => $responseData) {
187 3
                if (is_a($exception, $exceptionClass)) {
188
                    /** @var string $responseMessage */
189 3
                    $responseMessage = $responseData['message'];
190
191 3
                    if (empty($responseMessage)) {
192 3
                        $responseMessage = $exception->getMessage();
193
                    }
194
195 3
                    if (!empty($responseData['flash-type'])) {
196
                        /** @var string $flashMessage */
197 1
                        $flashMessage = sprintf($responseData['flash-message'], $exception->getMessage());
198
199 1
                        $this->controllerHelper->addFlashMessage($flashMessage, $responseData['flash-type']);
200
                    }
201
202 3
                    if (!empty($responseData['redirect-route'])) {
203 2
                        $response = $this->controllerHelper->redirectToRoute(
204 2
                            $responseData['redirect-route'],
205 2
                            $this->argumentBuilder->buildArguments(
206 2
                                $responseData['redirect-route-parameters'],
207 2
                                $request
208
                            ),
209 2
                            $responseData['code']
210
                        );
211
212
                    } else {
213 3
                        $response = new Response($responseMessage, $responseData['code']);
214
                    }
215
                }
216
            }
217
218 5
            if (is_null($response)) {
219 2
                throw $exception;
220
            }
221
        }
222
223 7
        return $response;
224
    }
225
226
}
227