Completed
Push — v0.10 ( f42b97...309689 )
by Simonas
8s
created

DocumentParser::getProperties()   C

Complexity

Conditions 13
Paths 10

Size

Total Lines 41
Code Lines 20

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 41
rs 5.1234
cc 13
eloc 20
nc 10
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\ElasticsearchBundle\Mapping;
13
14
use Doctrine\Common\Annotations\AnnotationRegistry;
15
use Doctrine\Common\Annotations\Reader;
16
use ONGR\ElasticsearchBundle\Annotation\Document;
17
use ONGR\ElasticsearchBundle\Annotation\Inherit;
18
use ONGR\ElasticsearchBundle\Annotation\MultiField;
19
use ONGR\ElasticsearchBundle\Annotation\Property;
20
use ONGR\ElasticsearchBundle\Annotation\Skip;
21
use ONGR\ElasticsearchBundle\Annotation\Suggester\AbstractSuggesterProperty;
22
use ONGR\ElasticsearchBundle\Mapping\Proxy\ProxyFactory;
23
24
/**
25
 * Document parser used for reading document annotations.
26
 */
27
class DocumentParser
28
{
29
    /**
30
     * @const string
31
     */
32
    const SUGGESTER_PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Suggester\AbstractSuggesterProperty';
33
34
    /**
35
     * @const string
36
     */
37
    const PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Property';
38
39
    /**
40
     * @var Reader Used to read document annotations.
41
     */
42
    private $reader;
43
44
    /**
45
     * @var DocumentFinder Used to find documents.
46
     */
47
    private $finder;
48
49
    /**
50
     * @var array Contains gathered objects which later adds to documents.
51
     */
52
    private $objects = [];
53
54
    /**
55
     * @var array Document properties aliases.
56
     */
57
    private $aliases = [];
58
59
    /**
60
     * @var array Local cache for document properties.
61
     */
62
    private $properties = [];
63
64
    /**
65
     * @param Reader         $reader Used for reading annotations.
66
     * @param DocumentFinder $finder Used for resolving namespaces.
67
     */
68
    public function __construct(Reader $reader, DocumentFinder $finder)
69
    {
70
        $this->reader = $reader;
71
        $this->finder = $finder;
72
        $this->registerAnnotations();
73
    }
74
75
    /**
76
     * Parses documents by used annotations and returns mapping for elasticsearch with some extra metadata.
77
     *
78
     * @param \ReflectionClass $reflectionClass
79
     *
80
     * @return array|null
81
     */
82
    public function parse(\ReflectionClass $reflectionClass)
83
    {
84
        /** @var Document $class */
85
        $class = $this
86
            ->reader
87
            ->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Document');
88
89
        if ($class !== null && $class->create) {
90
            if ($class->parent !== null) {
91
                $parent = $this->getDocumentParentType(
92
                    new \ReflectionClass($this->finder->getNamespace($class->parent))
93
                );
94
            } else {
95
                $parent = null;
96
            }
97
            $type = $this->getDocumentType($reflectionClass, $class);
98
            $inherit = $this->getInheritedProperties($reflectionClass);
99
100
            $properties = $this->getProperties(
101
                $reflectionClass,
102
                array_merge($inherit, $this->getSkippedProperties($reflectionClass))
103
            );
104
105
            if (!empty($inherit)) {
106
                $properties = array_merge(
107
                    $properties,
108
                    $this->getProperties($reflectionClass->getParentClass(), $inherit, true)
109
                );
110
            }
111
112
            return [
113
                $type => [
114
                    'properties' => $properties,
115
                    'fields' => array_merge(
116
                        $class->dump(),
117
                        ['_parent' => $parent === null ? null : ['type' => $parent]]
118
                    ),
119
                    'aliases' => $this->getAliases($reflectionClass),
120
                    'objects' => $this->getObjects(),
121
                    'proxyNamespace' => ProxyFactory::getProxyNamespace($reflectionClass, true),
122
                    'namespace' => $reflectionClass->getName(),
123
                    'class' => $reflectionClass->getShortName(),
124
                ],
125
            ];
126
        }
127
128
        return null;
129
    }
130
131
    /**
132
     * Returns property annotation data from reader.
133
     *
134
     * @param \ReflectionProperty $property
135
     *
136
     * @return AbstractSuggesterProperty|Property
137
     */
138
    public function getPropertyAnnotationData($property)
139
    {
140
        $type = $this->reader->getPropertyAnnotation($property, self::PROPERTY_ANNOTATION);
141
        if ($type === null) {
142
            $type = $this->reader->getPropertyAnnotation($property, self::SUGGESTER_PROPERTY_ANNOTATION);
143
        }
144
145
        return $type;
146
    }
147
148
    /**
149
     * Returns objects used in document.
150
     *
151
     * @return array
152
     */
153
    private function getObjects()
154
    {
155
        return array_keys($this->objects);
156
    }
157
158
    /**
159
     * Finds aliases for every property used in document including parent classes.
160
     *
161
     * @param \ReflectionClass $reflectionClass
162
     *
163
     * @return array
164
     */
165
    private function getAliases(\ReflectionClass $reflectionClass)
166
    {
167
        $reflectionName = $reflectionClass->getName();
168
        if (array_key_exists($reflectionName, $this->aliases)) {
169
            return $this->aliases[$reflectionName];
170
        }
171
172
        $alias = [];
173
        /** @var \ReflectionProperty $property */
174
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) {
175
            $type = $this->getPropertyAnnotationData($property);
176
            if ($type !== null) {
177
                $alias[$type->name] = [
178
                    'propertyName' => $name,
179
                    'type' => $type->type,
180
                ];
181
                if ($type->objectName) {
182
                    $child = new \ReflectionClass($this->finder->getNamespace($type->objectName));
183
                    $alias[$type->name] = array_merge(
184
                        $alias[$type->name],
185
                        [
186
                            'multiple' => $type instanceof Property ? $type->multiple : false,
187
                            'aliases' => $this->getAliases($child),
188
                            'proxyNamespace' => ProxyFactory::getProxyNamespace($child, true),
189
                            'namespace' => $child->getName(),
190
                        ]
191
                    );
192
                }
193
            }
194
        }
195
196
        $this->aliases[$reflectionName] = $alias;
197
198
        return $this->aliases[$reflectionName];
199
    }
200
201
    /**
202
     * Registers annotations to registry so that it could be used by reader.
203
     */
204
    private function registerAnnotations()
205
    {
206
        $annotations = [
207
            'Document',
208
            'Property',
209
            'Object',
210
            'Nested',
211
            'MultiField',
212
            'Inherit',
213
            'Skip',
214
            'Suggester/CompletionSuggesterProperty',
215
            'Suggester/ContextSuggesterProperty',
216
            'Suggester/Context/CategoryContext',
217
            'Suggester/Context/GeoLocationContext',
218
        ];
219
220
        foreach ($annotations as $annotation) {
221
            AnnotationRegistry::registerFile(__DIR__ . "/../Annotation/{$annotation}.php");
222
        }
223
    }
224
225
    /**
226
     * Returns document parent.
227
     *
228
     * @param \ReflectionClass $reflectionClass
229
     *
230
     * @return string|null
231
     */
232 View Code Duplication
    private function getDocumentParentType(\ReflectionClass $reflectionClass)
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...
233
    {
234
        /** @var Document $class */
235
        $class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Document');
236
237
        return $class ? $this->getDocumentType($reflectionClass, $class) : null;
238
    }
239
240
    /**
241
     * @param \ReflectionClass $reflectionClass
242
     *
243
     * @return array
244
     */
245 View Code Duplication
    private function getSkippedProperties(\ReflectionClass $reflectionClass)
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...
246
    {
247
        /** @var Skip $class */
248
        $class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Skip');
249
250
        return $class === null ? [] : $class->value;
251
    }
252
253
    /**
254
     * @param \ReflectionClass $reflectionClass
255
     *
256
     * @return array
257
     */
258 View Code Duplication
    private function getInheritedProperties(\ReflectionClass $reflectionClass)
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...
259
    {
260
        /** @var Inherit $class */
261
        $class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Inherit');
262
263
        return $class === null ? [] : $class->value;
264
    }
265
266
    /**
267
     * Returns document type.
268
     *
269
     * @param \ReflectionClass $reflectionClass
270
     * @param Document         $document
271
     *
272
     * @return string
273
     */
274
    private function getDocumentType(\ReflectionClass $reflectionClass, Document $document)
275
    {
276
        return empty($document->type) ? $reflectionClass->getShortName() : $document->type;
277
    }
278
279
    /**
280
     * Returns all defined properties including private from parents.
281
     *
282
     * @param \ReflectionClass $reflectionClass
283
     *
284
     * @return array
285
     */
286
    private function getDocumentPropertiesReflection(\ReflectionClass $reflectionClass)
287
    {
288
        if (in_array($reflectionClass->getName(), $this->properties)) {
289
            return $this->properties[$reflectionClass->getName()];
290
        }
291
292
        $properties = [];
293
294
        foreach ($reflectionClass->getProperties() as $property) {
295
            if (!in_array($property->getName(), $properties)) {
296
                $properties[$property->getName()] = $property;
297
            }
298
        }
299
300
        $parentReflection = $reflectionClass->getParentClass();
301
        if ($parentReflection !== false) {
302
            $properties = array_merge(
303
                $properties,
304
                array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties)
305
            );
306
        }
307
308
        $this->properties[$reflectionClass->getName()] = $properties;
309
310
        return $properties;
311
    }
312
313
    /**
314
     * Returns properties of reflection class.
315
     *
316
     * @param \ReflectionClass $reflectionClass Class to read properties from.
317
     * @param array            $properties      Properties to skip.
318
     * @param bool             $flag            If false exludes properties, true only includes properties.
319
     *
320
     * @return array
321
     */
322
    private function getProperties(\ReflectionClass $reflectionClass, $properties = [], $flag = false)
323
    {
324
        $mapping = [];
325
        /** @var \ReflectionProperty $property */
326
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) {
327
            $type = $this->getPropertyAnnotationData($property);
328
329
            if ((in_array($name, $properties) && !$flag)
330
                || (!in_array($name, $properties) && $flag)
331
                || empty($type)
332
            ) {
333
                continue;
334
            }
335
336
            $maps = $type->dump();
337
338
            // Object.
339
            if (in_array($type->type, ['object', 'nested']) && !empty($type->objectName)) {
340
                $maps = array_replace_recursive($maps, $this->getObjectMapping($type->objectName));
341
            }
342
343
            // MultiField.
344
            if (isset($maps['fields']) && !in_array($type->type, ['object', 'nested'])) {
345
                $fieldsMap = [];
346
                /** @var MultiField $field */
347
                foreach ($maps['fields'] as $field) {
348
                    $fieldsMap[$field->name] = $field->dump();
349
                }
350
                $maps['fields'] = $fieldsMap;
351
            }
352
353
            // Suggestions.
354
            if ($type instanceof AbstractSuggesterProperty) {
355
                $this->getObjectMapping($type->objectName);
356
            }
357
358
            $mapping[$type->name] = $maps;
359
        }
360
361
        return $mapping;
362
    }
363
364
    /**
365
     * Returns object mapping.
366
     *
367
     * Loads from cache if it's already loaded.
368
     *
369
     * @param string $objectName
370
     *
371
     * @return array
372
     */
373
    private function getObjectMapping($objectName)
374
    {
375
        $namespace = $this->finder->getNamespace($objectName);
376
377
        if (array_key_exists($namespace, $this->objects)) {
378
            return $this->objects[$namespace];
379
        }
380
381
        $this->objects[$namespace] = $this->getRelationMapping(new \ReflectionClass($namespace));
382
383
        return $this->objects[$namespace];
384
    }
385
386
    /**
387
     * Returns relation mapping by its reflection.
388
     *
389
     * @param \ReflectionClass $reflectionClass
390
     *
391
     * @return array|null
392
     */
393
    private function getRelationMapping(\ReflectionClass $reflectionClass)
394
    {
395
        if ($this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Object')
396
            || $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Nested')
397
        ) {
398
            return ['properties' => $this->getProperties($reflectionClass)];
399
        }
400
401
        return null;
402
    }
403
}
404