Passed
Push — master ( 261f35...f81515 )
by Gerrit
09:50
created

GenericEntityCreateController   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 242
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 93.02%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 6
dl 0
loc 242
ccs 80
cts 86
cp 0.9302
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 36 2
A createEntity() 0 41 3
B findConstructorReflection() 0 54 7
A createEntityByConstructor() 0 27 5
A performPostCreationCalls() 0 19 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 Symfony\Component\Serializer\SerializerInterface;
20
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
21
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
22
use Webmozart\Assert\Assert;
23
use XSLTProcessor;
24
use DOMDocument;
25
use ReflectionClass;
26
use ReflectionMethod;
27
use ReflectionParameter;
28
use Psr\Container\ContainerInterface;
29
use ErrorException;
30
use ReflectionFunction;
31
use ReflectionFunctionAbstract;
32
use ReflectionObject;
33
use Addiks\SymfonyGenerics\Events\EntityInteractionEvent;
34
35
final class GenericEntityCreateController
36
{
37
38
    /**
39
     * @var ControllerHelperInterface
40
     */
41
    private $controllerHelper;
42
43
    /**
44
     * @var ContainerInterface
45
     */
46
    private $container;
47
48
    /**
49
     * @var string
50
     */
51
    private $entityClass;
52
53
    /**
54
     * @var array<string, array<string, mixed>>
55
     */
56
    private $calls = array();
57
58
    /**
59
     * @var string|null
60
     */
61
    private $factory = null;
62
63
    /**
64
     * @var array<string, mixed>
65
     */
66
    private $constructArguments = array();
67
68
    /**
69
     * @var ArgumentCompilerInterface
70
     */
71
    private $argumentBuilder;
72
73
    /**
74
     * @var string
75
     */
76
    private $successResponse;
77
78
    /**
79
     * @var string|null
80
     */
81
    private $authorizationAttribute;
82
83 16
    public function __construct(
84
        ControllerHelperInterface $controllerHelper,
85
        ArgumentCompilerInterface $argumentBuilder,
86
        ContainerInterface $container,
87
        array $options
88
    ) {
89 16
        Assert::null($this->controllerHelper);
90 16
        Assert::keyExists($options, 'entity-class');
91 15
        Assert::true(class_exists($options['entity-class']));
92
93 14
        $options = array_merge([
94 14
            'calls' => [],
95
            'success-response' => "object created",
96
            'factory' => null,
97
            'authorization-attribute' => null,
98
            'arguments' => []
99 14
        ], $options);
100
101 14
        $this->controllerHelper = $controllerHelper;
102 14
        $this->argumentBuilder = $argumentBuilder;
103 14
        $this->container = $container;
104 14
        $this->entityClass = $options['entity-class'];
105 14
        $this->successResponse = $options['success-response'];
106 14
        $this->factory = $options['factory'];
107 14
        $this->authorizationAttribute = $options['authorization-attribute'];
108 14
        $this->constructArguments = $options['arguments'];
109
110 14
        foreach ($options['calls'] as $methodName => $arguments) {
111
            /** @var array $arguments */
112
113 4
            Assert::isArray($arguments);
114 3
            Assert::true(method_exists($this->entityClass, $methodName));
115
116 1
            $this->calls[$methodName] = $arguments;
117
        }
118 11
    }
119
120 10
    public function createEntity(Request $request): Response
121
    {
122
        /** @var object|null $factoryObject */
123 10
        $factoryObject = null;
124
125 10
        if (!empty($this->authorizationAttribute)) {
126 1
            $this->controllerHelper->denyAccessUnlessGranted($this->authorizationAttribute, $request);
127
        }
128
129
        /** @var ReflectionFunctionAbstract $constructorReflection */
130 10
        $constructorReflection = $this->findConstructorReflection($factoryObject);
131
132
        /** @var array<int, mixed> $constructArguments */
133 6
        $constructArguments = $this->argumentBuilder->buildCallArguments(
134 6
            $constructorReflection,
135 6
            $this->constructArguments,
136 6
            $request
137
        );
138
139
        /** @var object $entity */
140 6
        $entity = $this->createEntityByConstructor($constructorReflection, $constructArguments, $factoryObject);
141
142 5
        $this->performPostCreationCalls($entity, $request);
143
144 5
        if (!empty($this->authorizationAttribute)) {
145 1
            $this->controllerHelper->denyAccessUnlessGranted($this->authorizationAttribute, $entity);
146
        }
147
148 4
        $this->controllerHelper->persistEntity($entity);
149 4
        $this->controllerHelper->flushORM();
150
151 4
        $this->controllerHelper->dispatchEvent("symfony_generics.entity_interaction", new EntityInteractionEvent(
152 4
            $this->entityClass,
153 4
            null, # TODO: get id via reflection
154 4
            $entity,
155 4
            "__construct",
156 4
            $constructArguments
157
        ));
158
159 4
        return new Response($this->successResponse, 200);
160
    }
161
162
    /**
163
     * @param object $factoryObject
164
     */
165 10
    private function findConstructorReflection(&$factoryObject = null): ReflectionFunctionAbstract
166
    {
167
        /** @var ReflectionFunctionAbstract|null $constructorReflection */
168 10
        $constructorReflection = null;
169
170 10
        if (!empty($this->factory)) {
171 7
            if (is_int(strpos($this->factory, '::'))) {
172 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...
173
174 7
                if (!empty($factoryClass)) {
175 6
                    if ($factoryClass[0] == '@') {
176
                        # Create by factory-service-object
177
178 6
                        $factoryObject = $this->container->get(substr($factoryClass, 1));
179
180 6
                        Assert::object($factoryObject, sprintf(
181 6
                            "Did not find service '%s'!",
182 6
                            substr($factoryClass, 1)
183
                        ));
184
185 5
                        $constructorReflection = (new ReflectionObject($factoryObject))->getMethod($factoryMethod);
186
187
                    } else {
188
                        # Create by static factory-method of other class
189
190 3
                        $constructorReflection = (new ReflectionClass($factoryClass))->getMethod($factoryMethod);
191
                    }
192
193
                } else {
194 1
                    throw new ErrorException(sprintf(
195 1
                        "Invalid constructor definition: '%s'!",
196 4
                        $this->factory
197
                    ));
198
                }
199
200
            } elseif (method_exists($this->entityClass, $this->factory)) {
201
                # Create by static factory method on entity class
202
203
                $constructorReflection = (new ReflectionClass($this->entityClass))->getMethod($this->factory);
204
205
            } elseif (function_exists($this->factory)) {
206
                # Create by factory function
207
208 3
                $constructorReflection = new ReflectionFunction($this->factory);
209
            }
210
211
        } else {
212
            # Create by calling the constructor directly
213
214 3
            $constructorReflection = (new ReflectionClass($this->entityClass))->getConstructor();
215
        }
216
217 6
        return $constructorReflection;
218
    }
219
220
    /**
221
     * @param object|null $factoryObject
222
     *
223
     * @return object
224
     */
225 6
    private function createEntityByConstructor(
226
        ReflectionFunctionAbstract $constructorReflection,
227
        array $constructArguments,
228
        $factoryObject
229
    ) {
230
        /** @var object|null $entity */
231 6
        $entity = null;
232
233 6
        if ($constructorReflection instanceof ReflectionMethod) {
234 6
            if ($constructorReflection->isConstructor()) {
235 3
                $entity = $constructorReflection->getDeclaringClass()->newInstanceArgs($constructArguments);
236
237 3
            } elseif ($constructorReflection->isStatic()) {
238
                $entity = $constructorReflection->invokeArgs(null, $constructArguments);
239
240
            } else {
241 6
                $entity = $constructorReflection->invokeArgs($factoryObject, $constructArguments);
242
            }
243
244
        } elseif ($constructorReflection instanceof ReflectionFunction) {
245
            $entity = $constructorReflection->invokeArgs($constructArguments);
246
        }
247
248 6
        Assert::isInstanceOf($entity, $this->entityClass);
249
250 5
        return $entity;
251
    }
252
253
    /**
254
     * @param object $entity
255
     */
256 5
    private function performPostCreationCalls($entity, Request $request): void
257
    {
258 5
        $classReflection = new ReflectionClass($this->entityClass);
259
260 5
        foreach ($this->calls as $methodName => $callArgumentConfiguration) {
261
            /** @var array $callArgumentConfiguration */
262
263
            /** @var ReflectionMethod $methodReflection */
264 1
            $methodReflection = $classReflection->getMethod($methodName);
265
266 1
            $callArguments = $this->argumentBuilder->buildCallArguments(
267 1
                $methodReflection,
268 1
                $callArgumentConfiguration,
269 1
                $request
270
            );
271
272 1
            $methodReflection->invoke($entity, $callArguments);
273
        }
274 5
    }
275
276
}
277