MappingAnnotationDriver::loadRDMMetadataForClass()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 35
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 13
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 35
ccs 14
cts 14
cp 1
crap 5
rs 9.5222
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
 * @license GPL-3.0
8
 * @author Gerrit Addiks <[email protected]>
9
 */
10
11
namespace Addiks\RDMBundle\Mapping\Drivers;
12
13
use Doctrine\Common\Annotations\Reader;
14
use ReflectionClass;
15
use ReflectionProperty;
16
use Addiks\RDMBundle\Mapping\Drivers\MappingDriverInterface;
17
use Addiks\RDMBundle\Mapping\MappingInterface;
18
use Addiks\RDMBundle\Mapping\EntityMapping;
19
use Addiks\RDMBundle\Mapping\Annotation\Service;
20
use Addiks\RDMBundle\Mapping\ServiceMapping;
21
use Addiks\RDMBundle\Mapping\EntityMappingInterface;
22
use Addiks\RDMBundle\Mapping\Annotation\Choice;
23
use Addiks\RDMBundle\Mapping\ChoiceMapping;
24
use Addiks\RDMBundle\Exception\InvalidMappingException;
25
use Doctrine\ORM\Mapping\Column as ColumnAnnotation;
26
use Doctrine\DBAL\Schema\Column as DBALColumn;
27
use Doctrine\DBAL\Types\Type;
28
use Addiks\RDMBundle\Mapping\ObjectMapping;
29
use Addiks\RDMBundle\Mapping\CallDefinitionInterface;
30
use Addiks\RDMBundle\Mapping\FieldMapping;
31
use Addiks\RDMBundle\Mapping\ArrayMapping;
32
use Addiks\RDMBundle\Mapping\Annotation\RDMObject;
33
use Addiks\RDMBundle\Mapping\Annotation\RDMArray;
34
use Symfony\Component\DependencyInjection\ContainerInterface;
35
use Webmozart\Assert\Assert;
36
37
final class MappingAnnotationDriver implements MappingDriverInterface
38
{
39
40
    /**
41
     * @var ContainerInterface
42
     */
43
    private $container;
44
45
    /**
46
     * @var Reader
47
     */
48
    private $annotationReader;
49
50 8
    public function __construct(
51
        ContainerInterface $container,
52
        Reader $annotationReader
53
    ) {
54 8
        $this->container = $container;
55 8
        $this->annotationReader = $annotationReader;
56
    }
57
58 7
    public function loadRDMMetadataForClass(string $className): ?EntityMappingInterface
59
    {
60
        /** @var ?EntityMappingInterface $mapping */
61 7
        $mapping = null;
62
63
        /** @var array<MappingInterface> $fieldMappings */
64 7
        $fieldMappings = array();
65
66 7
        $classReflection = new ReflectionClass($className);
67
68 7
        foreach ($classReflection->getProperties() as $propertyReflection) {
69
            /** @var ReflectionProperty $propertyReflection */
70
71
            /** @var string $fieldName */
72 7
            $fieldName = $propertyReflection->getName();
73
74
            /** @var array<object> $annotations */
75 7
            $annotations = $this->annotationReader->getPropertyAnnotations($propertyReflection);
76
77 7
            foreach ($annotations as $annotation) {
78
                /** @var object $annotation */
79
80 7
                $fieldMapping = $this->convertAnnotationToMapping($annotation, $fieldName, $className);
81
82 7
                if ($fieldMapping instanceof MappingInterface) {
83 7
                    $fieldMappings[$fieldName] = $fieldMapping;
84
                }
85
            }
86
        }
87
88 7
        if (!empty($fieldMappings)) {
89 7
            $mapping = new EntityMapping($className, $fieldMappings);
90
        }
91
92 7
        return $mapping;
93
    }
94
95
    /**
96
     * @param object $annotation
97
     */
98 7
    private function convertAnnotationToMapping(
99
        $annotation,
100
        string $fieldName,
101
        string $className,
102
        bool $convertFieldsOnRootLevel = false
103
    ): ?MappingInterface {
104
        /** @var ?MappingInterface $fieldMapping */
105 7
        $fieldMapping = null;
106
107 7
        if ($annotation instanceof Service) {
108 6
            $fieldMapping = new ServiceMapping(
109 6
                $this->container,
110 6
                $annotation->id,
111 6
                $annotation->lax,
112 6
                sprintf(
113
                    "in entity '%s' on field '%s'",
114
                    $className,
115
                    $fieldName
116
                )
117
            );
118
119 7
        } elseif ($annotation instanceof Choice) {
120
            /** @var string|null|ColumnAnnotation $column */
121 5
            $column = $annotation->column;
122
123 5
            if (!$column instanceof ColumnAnnotation) {
124
                /** @var string $columnName */
125 5
                $columnName = $fieldName;
126
127 5
                if (is_string($column)) {
128 5
                    $columnName = $column;
129
                }
130
131 5
                $column = new ColumnAnnotation();
132 5
                $column->name = $columnName;
133 5
                $column->type = 'string';
134 5
                $column->length = 255;
135 5
                $column->precision = 10;
136 5
                $column->nullable = $annotation->nullable;
137
            }
138
139 5
            $dbalColumn = new DBALColumn(
140 5
                (string) $column->name,
141 5
                Type::getType($column->type),
142
                [
143 5
                    'notnull'   => !$column->nullable,
144 5
                    'length'    => $column->length,
145 5
                    'precision' => $column->precision,
146 5
                    'scale'     => $column->scale,
147
                ]
148
            );
149
150
            /** @var array<MappingInterface>*/
151 5
            $choiceMappings = array();
152
153 5
            foreach ($annotation->choices as $determinator => $choiceAnnotation) {
154
                /** @var ?MappingInterface $choiceMapping */
155 4
                $choiceMapping = $this->convertAnnotationToMapping(
156
                    $choiceAnnotation,
157
                    $fieldName,
158
                    $className
159
                );
160
161 4
                if ($choiceMapping instanceof MappingInterface) {
162 4
                    $choiceMappings[$determinator] = $choiceMapping;
163
164
                } else {
165
                    throw new InvalidMappingException(sprintf(
166
                        "Invalid mapping on entity '%s' in field '%s' of choice-option '%s'!",
167
                        $className,
168
                        $fieldName,
169
                        $determinator
170
                    ));
171
                }
172
            }
173
174 5
            $fieldMapping = new ChoiceMapping(
175
                $dbalColumn,
176
                $choiceMappings,
177 5
                sprintf(
178
                    "in entity '%s' on field '%s'",
179
                    $className,
180
                    $fieldName
181
                )
182
            );
183
184 7
        } elseif ($annotation instanceof RDMObject) {
185
            /** @var array<MappingInterface> $subFieldMappings */
186 2
            $subFieldMappings = array();
187
188
            /** @var null|CallDefinitionInterface $factory */
189 2
            $factory = null;
190
191
            /** @var null|CallDefinitionInterface $serializer */
192 2
            $serializer = null;
193
194 2
            foreach ($annotation->fields as $subFieldName => $subFieldAnnotation) {
195
                /** @var object $fieldAnnotation */
196
197 2
                $subFieldMappings[$subFieldName] = $this->convertAnnotationToMapping(
198
                    $subFieldAnnotation,
199 2
                    $fieldName . "->" . $subFieldName,
200
                    $className,
201
                    true
202
                );
203
            }
204
205
            /** @var class-string|null $objectMappingClassName */
206 2
            $objectMappingClassName = $annotation->{"class"};
207
208 2
            Assert::classExists($objectMappingClassName);
209
210 2
            $fieldMapping = new ObjectMapping(
211
                $objectMappingClassName,
212
                $subFieldMappings,
213
                null, # TODO: column
214 2
                sprintf(
215
                    "in entity '%s' on field '%s'",
216
                    $className,
217
                    $fieldName
218
                ),
219
                $factory,
220
                $serializer
221
            );
222
223 7
        } elseif ($annotation instanceof RDMArray) {
224
            /** @var array<MappingInterface> $entryMappings */
225 2
            $entryMappings = array();
226
227 2
            foreach ($annotation->entries as $key => $entryAnnotaton) {
228 2
                $entryMappings[$key] = $this->convertAnnotationToMapping(
229
                    $entryAnnotaton,
230 2
                    $fieldName . "->" . $key,
231
                    $className,
232
                    true
233
                );
234
            }
235
236 2
            $fieldMapping = new ArrayMapping(
237
                $entryMappings,
238 2
                sprintf(
239
                    "in entity '%s' on field '%s'",
240
                    $className,
241
                    $fieldName
242
                )
243
            );
244
245 7
        } elseif ($annotation instanceof ColumnAnnotation && $convertFieldsOnRootLevel) {
246
            /** @var DBALColumn $dbalColumn */
247 2
            $dbalColumn = new DBALColumn(
248 2
                (string) $annotation->name,
249 2
                Type::getType($annotation->type ?? 'string'),
250
                [
251 2
                    'notnull'   => !$annotation->nullable,
252 2
                    'length'    => $annotation->length,
253 2
                    'precision' => $annotation->precision ?? 0,
254 2
                    'scale'     => $annotation->scale,
255
                ]
256
            );
257
258 2
            $fieldMapping = new FieldMapping(
259
                $dbalColumn,
260 2
                sprintf(
261
                    "in entity '%s' on field '%s'",
262
                    $className,
263
                    $fieldName
264
                )
265
            );
266
        }
267
268 7
        return $fieldMapping;
269
    }
270
271
}
272