Completed
Push — upgrade ( a26676...c30839 )
by Simonas
01:32
created

DocumentParser::getAliases()   F

Complexity

Conditions 17
Paths 234

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 85
rs 3.4939
c 0
b 0
f 0
cc 17
nc 234
nop 2

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\Index;
17
use ONGR\ElasticsearchBundle\Annotation\Embedded;
18
use ONGR\ElasticsearchBundle\Annotation\HashMap;
19
use ONGR\ElasticsearchBundle\Annotation\MetaField;
20
use ONGR\ElasticsearchBundle\Annotation\NestedType;
21
use ONGR\ElasticsearchBundle\Annotation\Property;
22
use Symfony\Component\Cache\DoctrineProvider;
23
24
/**
25
 * Document parser used for reading document annotations.
26
 */
27
class DocumentParser
28
{
29
    const PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Property';
30
    const EMBEDDED_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Embedded';
31
    const INDEX_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Index';
32
    const OBJECT_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\ObjectType';
33
    const NESTED_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\NestedType';
34
35
    // Meta fields
36
    const ID_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Id';
37
    const ROUTING_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Routing';
38
    const VERSION_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Version';
39
    const HASH_MAP_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\HashMap';
40
41
    private $reader;
42
43
    private $objects = [];
44
45
    private $aliases = [];
46
47
    private $properties = [];
48
49
    private $indexes = [];
50
51
    public function __construct(Reader $reader, DoctrineProvider $cache = null)
0 ignored issues
show
Unused Code introduced by
The parameter $cache is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
52
    {
53
        $this->reader = $reader;
54
        $this->registerAnnotations();
55
    }
56
57
    private function getIndexAnnotationData(\ReflectionClass $document)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
58
    {
59
        return $this->reader->getClassAnnotation($document, self::INDEX_ANNOTATION);
60
    }
61
62
    /**
63
     * Returns property annotation data from reader.
64
     *
65
     * @param \ReflectionProperty $property
66
     *
67
     * @return Property|object|null
68
     */
69 View Code Duplication
    private function getPropertyAnnotationData(\ReflectionProperty $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...
70
    {
71
        $result = $this->reader->getPropertyAnnotation($property, self::PROPERTY_ANNOTATION);
72
73
        if ($result !== null && $result->name === null) {
74
            $result->name = Caser::snake($property->getName());
75
        }
76
77
        return $result;
78
    }
79
80
    /**
81
     * Returns Embedded annotation data from reader.
82
     *
83
     * @param \ReflectionProperty $property
84
     *
85
     * @return Embedded|object|null
86
     */
87 View Code Duplication
    private function getEmbeddedAnnotationData(\ReflectionProperty $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...
88
    {
89
        $result = $this->reader->getPropertyAnnotation($property, self::EMBEDDED_ANNOTATION);
90
91
        if ($result !== null && $result->name === null) {
92
            $result->name = Caser::snake($property->getName());
93
        }
94
95
        return $result;
96
    }
97
98
    /**
99
     * Returns HashMap annotation data from reader.
100
     *
101
     * @param \ReflectionProperty $property
102
     *
103
     * @return HashMap|object|null
104
     */
105 View Code Duplication
    private function getHashMapAnnotationData(\ReflectionProperty $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...
106
    {
107
        $result = $this->reader->getPropertyAnnotation($property, self::HASH_MAP_ANNOTATION);
108
109
        if ($result !== null && $result->name === null) {
110
            $result->name = Caser::snake($property->getName());
111
        }
112
113
        return $result;
114
    }
115
116
    private function getMetaFieldAnnotationData(\ReflectionProperty $property): array
117
    {
118
        /** @var MetaField $annotation */
119
        $annotation = $this->reader->getPropertyAnnotation($property, self::ID_ANNOTATION);
120
        $annotation = $annotation ?: $this->reader->getPropertyAnnotation($property, self::ROUTING_ANNOTATION);
121
        $annotation = $annotation ?: $this->reader->getPropertyAnnotation($property, self::VERSION_ANNOTATION);
122
123
        if ($annotation === null) {
124
            return null;
125
        }
126
127
        $data = [
128
            'name' => $annotation->getName(),
129
            'settings' => $annotation->getSettings(),
130
        ];
131
132
        return $data;
133
    }
134
135
    /**
136
     * Finds aliases for every property used in document including parent classes.
137
     *
138
     * @param \ReflectionClass $reflectionClass
139
     * @param array            $metaFields
140
     *
141
     * @return array
142
     */
143
    private function getAliases(\ReflectionClass $reflectionClass, array &$metaFields = null)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
144
    {
145
        $reflectionName = $reflectionClass->getName();
146
147
        // We skip cache in case $metaFields is given. This should not affect performance
148
        // because for each document this method is called only once. For objects it might
149
        // be called few times.
150
        if ($metaFields === null && array_key_exists($reflectionName, $this->aliases)) {
151
            return $this->aliases[$reflectionName];
152
        }
153
154
        $alias = [];
155
156
        /** @var \ReflectionProperty[] $properties */
157
        $properties = $this->getDocumentPropertiesReflection($reflectionClass);
158
159
        foreach ($properties as $name => $property) {
160
            $directory = $this->guessDirName($property->getDeclaringClass());
0 ignored issues
show
Bug introduced by
The method guessDirName() does not seem to exist on object<ONGR\Elasticsearc...Mapping\DocumentParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
161
162
            $type = $this->getPropertyAnnotationData($property);
163
            $type = $type !== null ? $type : $this->getEmbeddedAnnotationData($property);
164
            $type = $type !== null ? $type : $this->getHashMapAnnotationData($property);
165
166
            if ($type === null && $metaFields !== null
167
                && ($metaData = $this->getMetaFieldAnnotationData($property, $directory)) !== null) {
0 ignored issues
show
Unused Code introduced by
The call to DocumentParser::getMetaFieldAnnotationData() has too many arguments starting with $directory.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168
                $metaFields[$metaData['name']] = $metaData['settings'];
169
                $type = new \stdClass();
170
                $type->name = $metaData['name'];
171
            }
172
            if ($type !== null) {
173
                $alias[$type->name] = [
174
                    'propertyName' => $name,
175
                ];
176
177
                if ($type instanceof Property) {
178
                    $alias[$type->name]['type'] = $type->type;
179
                }
180
181
                if ($type instanceof HashMap) {
182
                    $alias[$type->name]['type'] = HashMap::NAME;
183
                }
184
185
                $alias[$type->name][HashMap::NAME] = $type instanceof HashMap;
186
187
                switch (true) {
188
                    case $property->isPublic():
189
                        $propertyType = 'public';
190
                        break;
191
                    case $property->isProtected():
192
                    case $property->isPrivate():
193
                        $propertyType = 'private';
194
                        $alias[$type->name]['methods'] = $this->getMutatorMethods(
195
                            $reflectionClass,
196
                            $name,
197
                            $type instanceof Property ? $type->type : null
198
                        );
199
                        break;
200
                    default:
201
                        $message = sprintf(
202
                            'There is a wrong property type %s used in the class %s',
203
                            $name,
204
                            $reflectionName
205
                        );
206
                        throw new \LogicException($message);
207
                }
208
                $alias[$type->name]['propertyType'] = $propertyType;
209
210
                if ($type instanceof Embedded) {
211
                    $alias[$type->name] = array_merge(
212
                        $alias[$type->name],
213
                        [
214
                            'type' => $this->getObjectMapping($type->class)['type'],
215
                            'multiple' => $type->multiple,
0 ignored issues
show
Bug introduced by
The property multiple does not seem to exist in ONGR\ElasticsearchBundle\Annotation\Embedded.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
216
                            'aliases' => $this->getAliases($child, $metaFields),
0 ignored issues
show
Bug introduced by
The variable $child 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...
217
                            'namespace' => $child->getName(),
218
                        ]
219
                    );
220
                }
221
            }
222
        }
223
224
        $this->aliases[$reflectionName] = $alias;
225
226
        return $this->aliases[$reflectionName];
227
    }
228
229
    /**
230
     * Checks if class have setter and getter, and returns them in array.
231
     *
232
     * @param \ReflectionClass $reflectionClass
233
     * @param string           $property
234
     *
235
     * @return array
236
     */
237
    private function getMutatorMethods(\ReflectionClass $reflectionClass, $property, $propertyType)
238
    {
239
        $camelCaseName = ucfirst(Caser::camel($property));
240
        $setterName = 'set'.$camelCaseName;
241
        if (!$reflectionClass->hasMethod($setterName)) {
242
            $message = sprintf(
243
                'Missing %s() method in %s class. Add it, or change the property to public type.',
244
                $setterName,
245
                $reflectionClass->getName()
246
            );
247
            throw new \LogicException($message);
248
        }
249
250 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...
251
            return [
252
                'getter' => 'get' . $camelCaseName,
253
                'setter' => $setterName
254
            ];
255
        }
256
257
        if ($propertyType === 'boolean') {
258 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...
259
                return [
260
                    'getter' => 'is' . $camelCaseName,
261
                    'setter' => $setterName
262
                ];
263
            }
264
265
            $message = sprintf(
266
                'Missing %s() or %s() method in %s class. Add it, or change property to public.',
267
                'get'.$camelCaseName,
268
                'is'.$camelCaseName,
269
                $reflectionClass->getName()
270
            );
271
            throw new \LogicException($message);
272
        }
273
274
        $message = sprintf(
275
            'Missing %s() method in %s class. Add it, or change property to public.',
276
            'get'.$camelCaseName,
277
            $reflectionClass->getName()
278
        );
279
        throw new \LogicException($message);
280
    }
281
282
    /**
283
     * Registers annotations to registry so that it could be used by reader.
284
     */
285
    private function registerAnnotations()
286
    {
287
        $annotations = [
288
            'Index',
289
            'Property',
290
            'Embedded',
291
            'ObjectType',
292
            'NestedType',
293
            'Id',
294
            'Routing',
295
            'Version',
296
            'HashMap',
297
        ];
298
299
        foreach ($annotations as $annotation) {
300
            AnnotationRegistry::registerFile(__DIR__ . "/../Annotation/{$annotation}.php");
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\Common\Annotati...egistry::registerFile() has been deprecated with message: this method is deprecated and will be removed in doctrine/annotations 2.0 autoloading should be deferred to the globally registered autoloader by then. For now, use @example AnnotationRegistry::registerLoader('class_exists')

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
301
        }
302
    }
303
304
    /**
305
     * Returns all defined properties including private from parents.
306
     *
307
     * @param \ReflectionClass $reflectionClass
308
     *
309
     * @return array
310
     */
311
    private function getDocumentPropertiesReflection(\ReflectionClass $reflectionClass)
312
    {
313
        if (in_array($reflectionClass->getName(), $this->properties)) {
314
            return $this->properties[$reflectionClass->getName()];
315
        }
316
317
        $properties = [];
318
319
        foreach ($reflectionClass->getProperties() as $property) {
320
            if (!in_array($property->getName(), $properties)) {
321
                $properties[$property->getName()] = $property;
322
            }
323
        }
324
325
        $parentReflection = $reflectionClass->getParentClass();
326
        if ($parentReflection !== false) {
327
            $properties = array_merge(
328
                $properties,
329
                array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties)
330
            );
331
        }
332
333
        $this->properties[$reflectionClass->getName()] = $properties;
334
335
        return $properties;
336
    }
337
338
    /**
339
     * Parses analyzers list from document mapping.
340
     *
341
     * @param \ReflectionClass $reflectionClass
342
     * @return array
343
     */
344
    private function getAnalyzers(\ReflectionClass $reflectionClass)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
345
    {
346
        $analyzers = [];
347
348
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) {
349
            $directory = $this->guessDirName($property->getDeclaringClass());
0 ignored issues
show
Bug introduced by
The method guessDirName() does not seem to exist on object<ONGR\Elasticsearc...Mapping\DocumentParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
350
351
            $type = $this->getPropertyAnnotationData($property);
352
            $type = $type !== null ? $type : $this->getEmbeddedAnnotationData($property);
353
354
            if ($type instanceof Embedded) {
355
                $analyzers = array_merge(
356
                    $analyzers,
357
                    $this->getAnalyzers(new \ReflectionClass($this->finder->getNamespace($type->class, $directory)))
0 ignored issues
show
Bug introduced by
The property finder does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
358
                );
359
            }
360
361
            if ($type instanceof Property) {
362
                if (isset($type->options['analyzer'])) {
363
                    $analyzers[] = $type->options['analyzer'];
364
                }
365
                if (isset($type->options['search_analyzer'])) {
366
                    $analyzers[] = $type->options['search_analyzer'];
367
                }
368
369
                if (isset($type->options['fields'])) {
370
                    foreach ($type->options['fields'] as $field) {
371
                        if (isset($field['analyzer'])) {
372
                            $analyzers[] = $field['analyzer'];
373
                        }
374
                        if (isset($field['search_analyzer'])) {
375
                            $analyzers[] = $field['search_analyzer'];
376
                        }
377
                    }
378
                }
379
            }
380
        }
381
        return array_unique($analyzers);
382
    }
383
384
    /**
385
     * Returns properties of reflection class.
386
     *
387
     * @param \ReflectionClass $reflectionClass Class to read properties from.
388
     * @param array            $properties      Properties to skip.
389
     * @param bool             $flag            If false exludes properties, true only includes properties.
390
     *
391
     * @return array
392
     */
393
    private function getProperties(\ReflectionClass $reflectionClass, $properties = [], $flag = false)
394
    {
395
        $mapping = [];
396
397
        /** @var \ReflectionProperty $property */
398
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) {
399
            $directory = $this->guessDirName($property->getDeclaringClass());
0 ignored issues
show
Bug introduced by
The method guessDirName() does not seem to exist on object<ONGR\Elasticsearc...Mapping\DocumentParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Unused Code introduced by
$directory is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
400
401
            $type = $this->getPropertyAnnotationData($property);
402
            $type = $type !== null ? $type : $this->getEmbeddedAnnotationData($property);
403
            $type = $type !== null ? $type : $this->getHashMapAnnotationData($property);
404
405
            if ((in_array($name, $properties) && !$flag)
406
                || (!in_array($name, $properties) && $flag)
407
                || empty($type)
408
            ) {
409
                continue;
410
            }
411
412
            $map = $type->dump();
413
414
            // Inner object
415
            if ($type instanceof Embedded) {
416
                $map = array_replace_recursive($map, $this->getObjectMapping($type->class));
417
            }
418
419
            // HashMap object
420
            if ($type instanceof HashMap) {
421
                $map = array_replace_recursive($map, [
422
                    'type' => NestedType::NAME,
423
                    'dynamic' => true,
424
                ]);
425
            }
426
427
            // If there is set some Raw options, it will override current ones.
428
            if (isset($map['options'])) {
429
                $options = $map['options'];
430
                unset($map['options']);
431
                $map = array_merge($map, $options);
432
            }
433
434
            $mapping[$type->name] = $map;
435
        }
436
437
        return $mapping;
438
    }
439
440
    private function getObjectMapping(string $namespace): array
441
    {
442
        if (array_key_exists($namespace, $this->objects)) {
443
            return $this->objects[$namespace];
444
        }
445
446
        $reflectionClass = new \ReflectionClass($namespace);
447
448
        switch (true) {
449
            case $this->reader->getClassAnnotation($reflectionClass, self::OBJECT_ANNOTATION):
450
                $type = 'object';
451
                break;
452
            case $this->reader->getClassAnnotation($reflectionClass, self::NESTED_ANNOTATION):
453
                $type = 'nested';
454
                break;
455
            default:
456
                throw new \LogicException(
457
                    sprintf(
458
                        '%s should have @ObjectType or @NestedType annotation to be used as embeddable object.',
459
                        $namespace
460
                    )
461
                );
462
        }
463
464
        $this->objects[$namespace] = [
465
            'type' => $type,
466
            'properties' => $this->getProperties($reflectionClass),
467
        ];
468
469
        return $this->objects[$namespace];
470
    }
471
}
472