Passed
Push — master ( a64949...af41cd )
by Gerrit
01:55
created

performPostCreationCalls()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 2
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\API;
14
15
use Addiks\SymfonyGenerics\Controllers\ControllerHelperInterface;
16
use Addiks\SymfonyGenerics\Services\ArgumentCompilerInterface;
17
use Symfony\Component\HttpFoundation\Response;
18
use Symfony\Component\HttpFoundation\Request;
19
use Webmozart\Assert\Assert;
20
use ReflectionClass;
21
use ReflectionMethod;
22
use Psr\Container\ContainerInterface;
23
use ErrorException;
24
use ReflectionFunction;
25
use ReflectionFunctionAbstract;
26
use ReflectionObject;
27
use Addiks\SymfonyGenerics\Events\EntityInteractionEvent;
28
29
final class GenericEntityCreateController
30
{
31
32
    /**
33
     * @var ControllerHelperInterface
34
     */
35
    private $controllerHelper;
36
37
    /**
38
     * @var ContainerInterface
39
     */
40
    private $container;
41
42
    /**
43
     * @var string
44
     */
45
    private $entityClass;
46
47
    /**
48
     * @var array<string, array<string, mixed>>
49
     */
50
    private $calls = array();
51
52
    /**
53
     * @var string|null
54
     */
55
    private $factory = null;
56
57
    /**
58
     * @var array<string, mixed>
59
     */
60
    private $constructArguments = array();
61
62
    /**
63
     * @var ArgumentCompilerInterface
64
     */
65
    private $argumentBuilder;
66
67
    /**
68
     * @var string
69
     */
70
    private $successResponse;
71
72
    /**
73
     * @var string|null
74
     */
75
    private $authorizationAttribute;
76
77
    /**
78
     * @var string|null
79
     */
80
    private $successRedirectRoute;
81
82
    /**
83
     * @var array
84
     */
85
    private $successRedirectArguments;
86
87
    /**
88
     * @var int
89
     */
90
    private $successRedirectStatus;
91
92 18
    public function __construct(
93
        ControllerHelperInterface $controllerHelper,
94
        ArgumentCompilerInterface $argumentBuilder,
95
        ContainerInterface $container,
96
        array $options
97
    ) {
98 18
        Assert::null($this->controllerHelper);
99 18
        Assert::keyExists($options, 'entity-class');
100
101
        /** @var int $defaultRedirectStatus */
102 17
        $defaultRedirectStatus = 303;
103
104 17
        $options = array_merge([
105 17
            'calls' => [],
106 17
            'success-response' => "object created",
107
            'factory' => null,
108
            'authorization-attribute' => null,
109
            'arguments' => [],
110
            'success-redirect' => null,
111
            'success-redirect-arguments' => [],
112 17
            'success-redirect-status' => $defaultRedirectStatus,
113 17
        ], $options);
114
115 17
        $this->controllerHelper = $controllerHelper;
116 17
        $this->argumentBuilder = $argumentBuilder;
117 17
        $this->container = $container;
118 17
        $this->entityClass = $options['entity-class'];
119 17
        $this->successResponse = $options['success-response'];
120 17
        $this->factory = $options['factory'];
121 17
        $this->authorizationAttribute = $options['authorization-attribute'];
122 17
        $this->constructArguments = $options['arguments'];
123 17
        $this->successRedirectRoute = $options['success-redirect'];
124 17
        $this->successRedirectArguments = $options['success-redirect-arguments'];
125 17
        $this->successRedirectStatus = $options['success-redirect-status'];
126
127 17
        foreach ($options['calls'] as $methodName => $arguments) {
128
            /** @var array $arguments */
129
130 4
            Assert::isArray($arguments);
131 3
            Assert::true(method_exists($this->entityClass, $methodName));
132
133 1
            $this->calls[$methodName] = $arguments;
134
        }
135 14
    }
136
137 2
    public function __invoke(): Response
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->createEntity($request);
145
    }
146
147 12
    public function createEntity(Request $request): Response
148
    {
149
        /** @var object|null $factoryObject */
150 12
        $factoryObject = null;
151
152 12
        if (!empty($this->authorizationAttribute)) {
153 1
            $this->controllerHelper->denyAccessUnlessGranted($this->authorizationAttribute, $request);
154
        }
155
156
        /** @var ReflectionFunctionAbstract $constructorReflection */
157 12
        $constructorReflection = $this->findConstructorReflection($factoryObject);
158
159
        /** @var array<int, mixed> $constructArguments */
160 8
        $constructArguments = $this->argumentBuilder->buildCallArguments(
161 8
            $constructorReflection,
162 8
            $this->constructArguments,
163 8
            $request
164
        );
165
166
        /** @var object $entity */
167 8
        $entity = $this->createEntityByConstructor($constructorReflection, $constructArguments, $factoryObject);
168
169 7
        $this->performPostCreationCalls($entity, $request);
170
171 7
        if (!empty($this->authorizationAttribute)) {
172 1
            $this->controllerHelper->denyAccessUnlessGranted($this->authorizationAttribute, $entity);
173
        }
174
175 6
        $this->controllerHelper->persistEntity($entity);
176
177 6
        $this->controllerHelper->dispatchEvent("symfony_generics.entity_interaction", new EntityInteractionEvent(
178 6
            $this->entityClass,
179 6
            null, # TODO: get id via reflection
180 6
            $entity,
181 6
            "__construct",
182 6
            $constructArguments
183
        ));
184
185 6
        $this->controllerHelper->flushORM();
186
187 6
        if (!empty($this->successRedirectRoute)) {
188
            /** @var array $redirectArguments */
189 1
            $redirectArguments = $this->argumentBuilder->buildArguments(
190 1
                $this->successRedirectArguments,
191 1
                $request
192
            );
193 1
            $redirectArguments['entityId'] = $entity->getId(); # TODO: getId might not always exist! get id via doctrine
194
195 1
            return $this->controllerHelper->redirectToRoute(
196 1
                $this->successRedirectRoute,
197 1
                $redirectArguments,
198 1
                $this->successRedirectStatus
199
            );
200
        }
201
202 5
        return new Response($this->successResponse, 200);
203
    }
204
205
    /**
206
     * @param object $factoryObject
207
     */
208 12
    private function findConstructorReflection(&$factoryObject = null): ReflectionFunctionAbstract
209
    {
210
        /** @var ReflectionFunctionAbstract|null $constructorReflection */
211 12
        $constructorReflection = null;
212
213 12
        if (!empty($this->factory)) {
214 7
            if (is_int(strpos($this->factory, '::'))) {
215 7
                [$factoryClass, $factoryMethod] = explode('::', $this->factory, 2);
0 ignored issues
show
Bug introduced by
The variable $factoryClass does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $factoryMethod does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
216
217 7
                if (!empty($factoryClass)) {
218 6
                    if ($factoryClass[0] == '@') {
219
                        # Create by factory-service-object
220
221 6
                        $factoryObject = $this->container->get(substr($factoryClass, 1));
222
223 6
                        Assert::object($factoryObject, sprintf(
224 6
                            "Did not find service '%s'!",
225 6
                            substr($factoryClass, 1)
226
                        ));
227
228 5
                        $constructorReflection = (new ReflectionObject($factoryObject))->getMethod($factoryMethod);
229
230
                    } else {
231
                        # Create by static factory-method of other class
232
233 3
                        $constructorReflection = (new ReflectionClass($factoryClass))->getMethod($factoryMethod);
234
                    }
235
236
                } else {
237 1
                    throw new ErrorException(sprintf(
238 1
                        "Invalid constructor definition: '%s'!",
239 4
                        $this->factory
240
                    ));
241
                }
242
243
            } elseif (method_exists($this->entityClass, $this->factory)) {
244
                # Create by static factory method on entity class
245
246
                $constructorReflection = (new ReflectionClass($this->entityClass))->getMethod($this->factory);
247
248
            } elseif (function_exists($this->factory)) {
249
                # Create by factory function
250
251 3
                $constructorReflection = new ReflectionFunction($this->factory);
252
            }
253
254
        } else {
255
            # Create by calling the constructor directly
256
257 5
            $constructorReflection = (new ReflectionClass($this->entityClass))->getConstructor();
258
        }
259
260 8
        return $constructorReflection;
261
    }
262
263
    /**
264
     * @param object|null $factoryObject
265
     *
266
     * @return object
267
     */
268 8
    private function createEntityByConstructor(
269
        ReflectionFunctionAbstract $constructorReflection,
270
        array $constructArguments,
271
        $factoryObject
272
    ) {
273
        /** @var object|null $entity */
274 8
        $entity = null;
275
276 8
        if ($constructorReflection instanceof ReflectionMethod) {
277 8
            if ($constructorReflection->isConstructor()) {
278 5
                $entity = $constructorReflection->getDeclaringClass()->newInstanceArgs($constructArguments);
279
280 3
            } elseif ($constructorReflection->isStatic()) {
281
                $entity = $constructorReflection->invokeArgs(null, $constructArguments);
282
283
            } else {
284 8
                $entity = $constructorReflection->invokeArgs($factoryObject, $constructArguments);
285
            }
286
287
        } elseif ($constructorReflection instanceof ReflectionFunction) {
288
            $entity = $constructorReflection->invokeArgs($constructArguments);
289
        }
290
291 8
        Assert::isInstanceOf($entity, $this->entityClass);
292
293 7
        return $entity;
294
    }
295
296
    /**
297
     * @param object $entity
298
     */
299 7
    private function performPostCreationCalls($entity, Request $request): void
300
    {
301 7
        $classReflection = new ReflectionClass($this->entityClass);
302
303 7
        foreach ($this->calls as $methodName => $callArgumentConfiguration) {
304
            /** @var array $callArgumentConfiguration */
305
306
            /** @var ReflectionMethod $methodReflection */
307 1
            $methodReflection = $classReflection->getMethod($methodName);
308
309 1
            $callArguments = $this->argumentBuilder->buildCallArguments(
310 1
                $methodReflection,
311 1
                $callArgumentConfiguration,
312 1
                $request
313
            );
314
315 1
            $methodReflection->invokeArgs($entity, $callArguments);
316
        }
317 7
    }
318
319
}
320