Passed
Push — master ( 5bc951...af3bd6 )
by Gerrit
01:59
created

createEntityByConstructor()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.3906

Importance

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