Completed
Pull Request — master (#519)
by Mantas
08:11
created

DocumentParser::getAliases()   C

Complexity

Conditions 11
Paths 62

Size

Total Lines 64
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 2
Metric Value
c 7
b 0
f 2
dl 0
loc 64
rs 6.0563
cc 11
eloc 42
nc 62
nop 1

How to fix   Long Method    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\Embedded;
18
use ONGR\ElasticsearchBundle\Annotation\MetaField;
19
use ONGR\ElasticsearchBundle\Annotation\Property;
20
21
/**
22
 * Document parser used for reading document annotations.
23
 */
24
class DocumentParser
25
{
26
    const META_FIELD_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\MetaField';
27
    const PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Property';
28
    const EMBEDDED_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Embedded';
29
    const DOCUMENT_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Document';
30
    const OBJECT_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Object';
31
    const NESTED_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Nested';
32
33
    /**
34
     * @var Reader Used to read document annotations.
35
     */
36
    private $reader;
37
38
    /**
39
     * @var DocumentFinder Used to find documents.
40
     */
41
    private $finder;
42
43
    /**
44
     * @var array Contains gathered objects which later adds to documents.
45
     */
46
    private $objects = [];
47
48
    /**
49
     * @var array Document properties aliases.
50
     */
51
    private $aliases = [];
52
53
    /**
54
     * @var array Local cache for document properties.
55
     */
56
    private $properties = [];
57
58
    /**
59
     * @param Reader         $reader Used for reading annotations.
60
     * @param DocumentFinder $finder Used for resolving namespaces.
61
     */
62
    public function __construct(Reader $reader, DocumentFinder $finder)
63
    {
64
        $this->reader = $reader;
65
        $this->finder = $finder;
66
        $this->registerAnnotations();
67
    }
68
69
    /**
70
     * Parses documents by used annotations and returns mapping for elasticsearch with some extra metadata.
71
     *
72
     * @param \ReflectionClass $document
73
     *
74
     * @return array
75
     */
76
    public function parse(\ReflectionClass $document)
77
    {
78
        /** @var Document $class */
79
        $class = $this
80
            ->reader
81
            ->getClassAnnotation($document, self::DOCUMENT_ANNOTATION);
82
83
        if ($class !== null) {
84
            if ($class->parent !== null) {
85
                $parent = $this->getDocumentType($class->parent);
86
            } else {
87
                $parent = null;
88
            }
89
90
            $properties = $this->getProperties($document);
91
92
            if ($class->type) {
93
                $documentType = $class->type;
94
            } else {
95
                $documentType = $document->getShortName();
96
            }
97
98
            return [
99
                'type' => $documentType,
100
                'properties' => $properties,
101
                'fields' => array_filter(
102
                    array_merge(
103
                        $class->dump(),
104
                        ['_parent' => $parent === null ? null : ['type' => $parent]]
105
                    )
106
                ),
107
                'aliases' => $this->getAliases($document),
108
                'objects' => $this->getObjects(),
109
                'namespace' => $document->getName(),
110
                'class' => $document->getShortName(),
111
            ];
112
        }
113
114
        return [];
115
    }
116
117
    /**
118
     * Returns document annotation data from reader.
119
     *
120
     * @param \ReflectionClass $document
121
     *
122
     * @return Document|null
123
     */
124
    public function getDocumentAnnotationData($document)
125
    {
126
        return $this->reader->getClassAnnotation($document, self::DOCUMENT_ANNOTATION);
127
    }
128
129
    /**
130
     * Returns property annotation data from reader.
131
     *
132
     * @param \ReflectionProperty $property
133
     *
134
     * @return Property|null
135
     */
136 View Code Duplication
    public function getPropertyAnnotationData($property)
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...
137
    {
138
        $result = $this->reader->getPropertyAnnotation($property, self::PROPERTY_ANNOTATION);
139
140
        if ($result !== null && $result->name === null) {
141
            $result->name = Caser::snake($property->getName());
142
        }
143
144
        return $result;
145
    }
146
147
    /**
148
     * Returns Embedded annotation data from reader.
149
     *
150
     * @param \ReflectionProperty $property
151
     *
152
     * @return Embedded|null
153
     */
154 View Code Duplication
    public function getEmbeddedAnnotationData($property)
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...
155
    {
156
        $result = $this->reader->getPropertyAnnotation($property, self::EMBEDDED_ANNOTATION);
157
158
        if ($result !== null && $result->name === null) {
159
            $result->name = Caser::snake($property->getName());
160
        }
161
162
        return $result;
163
    }
164
165
    /**
166
     * Returns meta field annotation data from reader.
167
     *
168
     * @param \ReflectionProperty $property
169
     *
170
     * @return MetaField|null
171
     */
172
    public function getMetaFieldAnnotationData($property)
173
    {
174
        return $this->reader->getPropertyAnnotation($property, self::META_FIELD_ANNOTATION);
175
    }
176
177
    /**
178
     * Returns objects used in document.
179
     *
180
     * @return array
181
     */
182
    private function getObjects()
183
    {
184
        return array_keys($this->objects);
185
    }
186
187
    /**
188
     * Finds aliases for every property used in document including parent classes.
189
     *
190
     * @param \ReflectionClass $reflectionClass
191
     *
192
     * @return array
193
     */
194
    private function getAliases(\ReflectionClass $reflectionClass)
195
    {
196
        $reflectionName = $reflectionClass->getName();
197
        if (array_key_exists($reflectionName, $this->aliases)) {
198
            return $this->aliases[$reflectionName];
199
        }
200
201
        $alias = [];
202
203
        /** @var \ReflectionProperty[] $properties */
204
        $properties = $this->getDocumentPropertiesReflection($reflectionClass);
205
206
        foreach ($properties as $name => $property) {
207
            $type = $this->getPropertyAnnotationData($property);
208
            $type = $type !== null ? $type : $this->getEmbeddedAnnotationData($property);
209
            $type = $type !== null ? $type : $this->getMetaFieldAnnotationData($property);
210
            if ($type !== null) {
211
                $alias[$type->name] = [
212
                    'propertyName' => $name,
213
                ];
214
215
                if ($type instanceof Property) {
216
                    $alias[$type->name]['type'] = $type->type;
217
                }
218
219
                switch (true) {
220
                    case $property->isPublic():
221
                        $propertyType = 'public';
222
                        break;
223
                    case $property->isProtected():
224
                    case $property->isPrivate():
225
                        $propertyType = 'private';
226
                        $alias[$type->name]['methods'] = $this->getMutatorMethods($reflectionClass, $name, $type->type);
227
                        break;
228
                    default:
229
                        $message = sprintf(
230
                            'Wrong property %s type of %s class types cannot '.
231
                            'be static or abstract.',
232
                            $name,
233
                            $reflectionName
234
                        );
235
                        throw new \LogicException($message);
236
                }
237
                $alias[$type->name]['propertyType'] = $propertyType;
238
239
                if ($type instanceof Embedded) {
240
                    $child = new \ReflectionClass($this->finder->getNamespace($type->class));
241
                    $alias[$type->name] = array_merge(
242
                        $alias[$type->name],
243
                        [
244
                            'type' => $this->getInnerObjectType($type->class),
245
                            'multiple' => $type->multiple,
246
                            'aliases' => $this->getAliases($child),
247
                            'namespace' => $child->getName(),
248
                        ]
249
                    );
250
                }
251
            }
252
        }
253
254
        $this->aliases[$reflectionName] = $alias;
255
256
        return $this->aliases[$reflectionName];
257
    }
258
259
    /**
260
     * Checks if class have setter and getter, and returns them in array.
261
     *
262
     * @param \ReflectionClass $reflectionClass
263
     * @param string           $property
264
     *
265
     * @return array
266
     */
267
    private function getMutatorMethods(\ReflectionClass $reflectionClass, $property, $propertyType)
268
    {
269
        $camelCaseName = ucfirst(Caser::camel($property));
270
        $setterName = 'set'.$camelCaseName;
271
        if (!$reflectionClass->hasMethod($setterName)) {
272
            $message = sprintf(
273
                'Missing %s() method in %s class. Add it, or change property to public.',
274
                $setterName,
275
                $reflectionClass->getName()
276
            );
277
            throw new \LogicException($message);
278
        }
279
280 View Code Duplication
        if ($reflectionClass->hasMethod('get'.$camelCaseName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
281
            return [
282
                'getter' => 'get' . $camelCaseName,
283
                'setter' => $setterName
284
            ];
285
        }
286
287
        if ($propertyType === 'boolean') {
288 View Code Duplication
            if ($reflectionClass->hasMethod('is' . $camelCaseName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
289
                return [
290
                    'getter' => 'is' . $camelCaseName,
291
                    'setter' => $setterName
292
                ];
293
            }
294
295
            $message = sprintf(
296
                'Missing %s() or %s() method in %s class. Add it, or change property to public.',
297
                'get'.$camelCaseName,
298
                'is'.$camelCaseName,
299
                $reflectionClass->getName()
300
            );
301
            throw new \LogicException($message);
302
        }
303
304
        $message = sprintf(
305
            'Missing %s() method in %s class. Add it, or change property to public.',
306
            'get'.$camelCaseName,
307
            $reflectionClass->getName()
308
        );
309
        throw new \LogicException($message);
310
    }
311
312
    /**
313
     * Registers annotations to registry so that it could be used by reader.
314
     */
315
    private function registerAnnotations()
316
    {
317
        $annotations = [
318
            'Document',
319
            'MetaField',
320
            'Property',
321
            'Embedded',
322
            'Object',
323
            'Nested',
324
        ];
325
326
        foreach ($annotations as $annotation) {
327
            AnnotationRegistry::registerFile(__DIR__ . "/../Annotation/{$annotation}.php");
328
        }
329
    }
330
331
    /**
332
     * Returns document type.
333
     *
334
     * @param string $document Format must be like AcmeBundle:Document.
335
     *
336
     * @return string
337
     */
338
    private function getDocumentType($document)
339
    {
340
        $namespace = $this->finder->getNamespace($document);
341
        $reflectionClass = new \ReflectionClass($namespace);
342
        $document = $this->getDocumentAnnotationData($reflectionClass);
343
344
        return empty($document->type) ? $reflectionClass->getShortName() : $document->type;
345
    }
346
347
    /**
348
     * Returns all defined properties including private from parents.
349
     *
350
     * @param \ReflectionClass $reflectionClass
351
     *
352
     * @return array
353
     */
354
    private function getDocumentPropertiesReflection(\ReflectionClass $reflectionClass)
355
    {
356
        if (in_array($reflectionClass->getName(), $this->properties)) {
357
            return $this->properties[$reflectionClass->getName()];
358
        }
359
360
        $properties = [];
361
362
        foreach ($reflectionClass->getProperties() as $property) {
363
            if (!in_array($property->getName(), $properties)) {
364
                $properties[$property->getName()] = $property;
365
            }
366
        }
367
368
        $parentReflection = $reflectionClass->getParentClass();
369
        if ($parentReflection !== false) {
370
            $properties = array_merge(
371
                $properties,
372
                array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties)
373
            );
374
        }
375
376
        $this->properties[$reflectionClass->getName()] = $properties;
377
378
        return $properties;
379
    }
380
381
    /**
382
     * Returns properties of reflection class.
383
     *
384
     * @param \ReflectionClass $reflectionClass Class to read properties from.
385
     * @param array            $properties      Properties to skip.
386
     * @param bool             $flag            If false exludes properties, true only includes properties.
387
     *
388
     * @return array
389
     */
390
    private function getProperties(\ReflectionClass $reflectionClass, $properties = [], $flag = false)
391
    {
392
        $mapping = [];
393
        /** @var \ReflectionProperty $property */
394
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) {
395
            $type = $this->getPropertyAnnotationData($property);
396
            $type = $type !== null ? $type : $this->getEmbeddedAnnotationData($property);
397
398
            if ((in_array($name, $properties) && !$flag)
399
                || (!in_array($name, $properties) && $flag)
400
                || empty($type)
401
            ) {
402
                continue;
403
            }
404
405
            $map = $type->dump();
406
407
            // Inner object
408
            if ($type instanceof Embedded) {
409
                $map['type'] = $this->getInnerObjectType($type->class);
410
                $map = array_replace_recursive($map, $this->getObjectMapping($type->class));
411
            }
412
413
            // If there is set some Raw options, it will override current ones.
414
            if (isset($map['options'])) {
415
                $options = $map['options'];
416
                unset($map['options']);
417
                $map = array_merge($map, $options);
418
            }
419
420
            $mapping[$type->name] = $map;
421
        }
422
423
        return $mapping;
424
    }
425
426
    /**
427
     * Returns object mapping.
428
     *
429
     * Loads from cache if it's already loaded.
430
     *
431
     * @param string $className
432
     *
433
     * @return array
434
     */
435
    private function getObjectMapping($className)
436
    {
437
        $namespace = $this->finder->getNamespace($className);
438
439
        if (array_key_exists($namespace, $this->objects)) {
440
            return $this->objects[$namespace];
441
        }
442
443
        $this->objects[$namespace] = $this->getRelationMapping(new \ReflectionClass($namespace));
444
445
        return $this->objects[$namespace];
446
    }
447
448
    /**
449
     * Returns relation mapping by its reflection.
450
     *
451
     * @param \ReflectionClass $reflectionClass
452
     *
453
     * @return array|null
454
     */
455
    private function getRelationMapping(\ReflectionClass $reflectionClass)
456
    {
457
        if ($this->reader->getClassAnnotation($reflectionClass, self::OBJECT_ANNOTATION)
458
            || $this->reader->getClassAnnotation($reflectionClass, self::NESTED_ANNOTATION)
459
        ) {
460
            return ['properties' => $this->getProperties($reflectionClass)];
461
        }
462
463
        return null;
464
    }
465
466
    /**
467
     * Returns inner object type by it's class name.
468
     *
469
     * @param string $className
470
     *
471
     * @return null|string
472
     */
473
    private function getInnerObjectType($className)
474
    {
475
        $reflection = new \ReflectionClass($this->finder->getNamespace($className));
476
477
        if ($this->reader->getClassAnnotation($reflection, self::OBJECT_ANNOTATION)) {
478
            return 'object';
479
        }
480
481
        if ($this->reader->getClassAnnotation($reflection, self::NESTED_ANNOTATION)) {
482
            return 'nested';
483
        }
484
485
        return null;
486
    }
487
}
488