Completed
Push — 6.1-dev ( cdf591...f3191c )
by
unknown
01:37 queued 10s
created

DocumentParser   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 8
dl 0
loc 366
rs 3.6
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getIndexAliasName() 0 7 1
A isDefaultIndex() 0 7 1
A getIndexAnnotation() 0 7 1
A getTypeName() 0 7 1
A getIndexMetadata() 0 25 3
A getDocumentNamespace() 0 12 3
A getParsedDocument() 0 7 1
B getClassMetadata() 0 56 7
C getPropertyMetadata() 0 63 12
B getAnalysisConfig() 0 28 6
B guessGetter() 0 27 7
A guessSetter() 0 16 4
A getListFromArrayByKey() 0 19 4
A getObjectMappingType() 0 20 3
A getDocumentPropertiesReflection() 0 26 5

How to fix   Complexity   

Complex Class

Complex classes like DocumentParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentParser, and based on these observations, apply Extract Interface, too.

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 Doctrine\Common\Cache\Cache;
17
use ONGR\ElasticsearchBundle\Annotation\AbstractAnnotation;
18
use ONGR\ElasticsearchBundle\Annotation\Embedded;
19
use ONGR\ElasticsearchBundle\Annotation\Id;
20
use ONGR\ElasticsearchBundle\Annotation\Index;
21
use ONGR\ElasticsearchBundle\Annotation\NestedType;
22
use ONGR\ElasticsearchBundle\Annotation\ObjectType;
23
use ONGR\ElasticsearchBundle\Annotation\PropertiesAwareInterface;
24
use ONGR\ElasticsearchBundle\Annotation\Property;
25
use ONGR\ElasticsearchBundle\DependencyInjection\Configuration;
26
27
/**
28
 * Document parser used for reading document annotations.
29
 */
30
class DocumentParser
31
{
32
    const OBJ_CACHED_FIELDS = 'ongr.obj_fields';
33
    const EMBEDDED_CACHED_FIELDS = 'ongr.embedded_fields';
34
    const ARRAY_CACHED_FIELDS = 'ongr.array_fields';
35
36
    private $reader;
37
    private $properties = [];
38
    private $analysisConfig = [];
39
    private $cache;
40
41
    public function __construct(Reader $reader, Cache $cache, array $analysisConfig = [])
42
    {
43
        $this->reader = $reader;
44
        $this->cache = $cache;
45
        $this->analysisConfig = $analysisConfig;
46
47
        #Fix for annotations loader until doctrine/annotations 2.0 will be released with the full autoload support.
48
        AnnotationRegistry::registerLoader('class_exists');
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\Common\Annotati...istry::registerLoader() 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...
49
    }
50
51
    public function getIndexAliasName(\ReflectionClass $class): string
52
    {
53
        /** @var Index $document */
54
        $document = $this->reader->getClassAnnotation($class, Index::class);
55
56
        return $document->alias ?? Caser::snake($class->getShortName());
57
    }
58
59
    public function isDefaultIndex(\ReflectionClass $class): bool
60
    {
61
        /** @var Index $document */
62
        $document = $this->reader->getClassAnnotation($class, Index::class);
63
64
        return $document->default;
65
    }
66
67
    public function getIndexAnnotation(\ReflectionClass $class)
68
    {
69
        /** @var Index $document */
70
        $document = $this->reader->getClassAnnotation($class, Index::class);
71
72
        return $document;
73
    }
74
75
    /**
76
     * @deprecated will be deleted in v7. Types are deleted from elasticsearch.
77
     */
78
    public function getTypeName(\ReflectionClass $class): string
79
    {
80
        /** @var Index $document */
81
        $document = $this->reader->getClassAnnotation($class, Index::class);
82
83
        return $document->typeName ?? '_doc';
0 ignored issues
show
Deprecated Code introduced by
The property ONGR\ElasticsearchBundle...tation\Index::$typeName has been deprecated with message: will be removed in v7 since there will be no more types in the indexes.

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
84
    }
85
86
    public function getIndexMetadata(\ReflectionClass $class): array
87
    {
88
        if ($class->isTrait()) {
89
            return [];
90
        }
91
92
        /** @var Index $document */
93
        $document = $this->reader->getClassAnnotation($class, Index::class);
94
95
        if ($document === null) {
96
            return [];
97
        }
98
99
        $settings = $document->getSettings();
100
        $settings['analysis'] = $this->getAnalysisConfig($class);
101
102
        return array_filter(array_map('array_filter', [
103
            'settings' => $settings,
104
            'mappings' => [
105
                $this->getTypeName($class) => [
0 ignored issues
show
Deprecated Code introduced by
The method ONGR\ElasticsearchBundle...ntParser::getTypeName() has been deprecated with message: will be deleted in v7. Types are deleted from elasticsearch.

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...
106
                    'properties' => array_filter($this->getClassMetadata($class))
107
                ]
108
            ]
109
        ]));
110
    }
111
112
    public function getDocumentNamespace(string $indexAlias): ?string
113
    {
114
        if ($this->cache->contains(Configuration::ONGR_INDEXES)) {
115
            $indexes = $this->cache->fetch(Configuration::ONGR_INDEXES);
116
117
            if (isset($indexes[$indexAlias])) {
118
                return $indexes[$indexAlias];
119
            }
120
        }
121
122
        return null;
123
    }
124
125
    public function getParsedDocument(\ReflectionClass $class): Index
126
    {
127
        /** @var Index $document */
128
        $document = $this->reader->getClassAnnotation($class, Index::class);
129
130
        return $document;
131
    }
132
133
    private function getClassMetadata(\ReflectionClass $class): array
134
    {
135
        $mapping = [];
136
        $objFields = null;
137
        $arrayFields = null;
138
        $embeddedFields = null;
139
140
        /** @var \ReflectionProperty $property */
141
        foreach ($this->getDocumentPropertiesReflection($class) as $name => $property) {
142
            $annotations = $this->reader->getPropertyAnnotations($property);
143
144
            /** @var AbstractAnnotation $annotation */
145
            foreach ($annotations as $annotation) {
146
                if (!$annotation instanceof PropertiesAwareInterface) {
147
                    continue;
148
                }
149
150
                $fieldMapping = $annotation->getSettings();
151
152
                if ($annotation instanceof Property) {
153
                    $fieldMapping['type'] = $annotation->type;
154
                    $fieldMapping['analyzer'] = $annotation->analyzer;
155
                    $fieldMapping['search_analyzer'] = $annotation->searchAnalyzer;
156
                    $fieldMapping['search_quote_analyzer'] = $annotation->searchQuoteAnalyzer;
157
                }
158
159
                if ($annotation instanceof Embedded) {
160
                    $embeddedClass = new \ReflectionClass($annotation->class);
161
                    $fieldMapping['type'] = $this->getObjectMappingType($embeddedClass);
162
                    $fieldMapping['properties'] = $this->getClassMetadata($embeddedClass);
163
                    $embeddedFields[$name] = $annotation->class;
164
                }
165
166
                $mapping[$annotation->getName() ?? Caser::snake($name)] = array_filter($fieldMapping);
167
                $objFields[$name] = $annotation->getName() ?? Caser::snake($name);
168
                $arrayFields[$annotation->getName() ?? Caser::snake($name)] = $name;
169
            }
170
        }
171
172
        //Embeded fields are option compared to the array or object mapping.
173
        if ($embeddedFields) {
174
            $cacheItem = $this->cache->fetch(self::EMBEDDED_CACHED_FIELDS) ?? [];
175
            $cacheItem[$class->getName()] = $embeddedFields;
176
            $t = $this->cache->save(self::EMBEDDED_CACHED_FIELDS, $cacheItem);
0 ignored issues
show
Unused Code introduced by
$t 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...
177
        }
178
179
        $cacheItem = $this->cache->fetch(self::ARRAY_CACHED_FIELDS) ?? [];
180
        $cacheItem[$class->getName()] = $arrayFields;
181
        $this->cache->save(self::ARRAY_CACHED_FIELDS, $cacheItem);
182
183
        $cacheItem = $this->cache->fetch(self::OBJ_CACHED_FIELDS) ?? [];
184
        $cacheItem[$class->getName()] = $objFields;
185
        $this->cache->save(self::OBJ_CACHED_FIELDS, $cacheItem);
186
187
        return $mapping;
188
    }
189
190
    public function getPropertyMetadata(\ReflectionClass $class, bool $subClass = false): array
191
    {
192
        if ($class->isTrait() || (!$this->reader->getClassAnnotation($class, Index::class) && !$subClass)) {
193
            return [];
194
        }
195
196
        $metadata = [];
197
198
        /** @var \ReflectionProperty $property */
199
        foreach ($this->getDocumentPropertiesReflection($class) as $name => $property) {
200
            /** @var AbstractAnnotation $annotation */
201
            foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
202
                if (!$annotation instanceof PropertiesAwareInterface) {
203
                    continue;
204
                }
205
206
                $propertyMetadata = [
207
                    'identifier' => false,
208
                    'class' => null,
209
                    'embeded' => false,
210
                    'type' => null,
211
                    'public' => $property->isPublic(),
212
                    'getter' => null,
213
                    'setter' => null,
214
                    'sub_properties' => []
215
                ];
216
217
                $name = $property->getName();
218
                $propertyMetadata['name'] = $name;
219
220
                if (!$propertyMetadata['public']) {
221
                    $propertyMetadata['getter'] = $this->guessGetter($class, $name);
222
                }
223
224
                if ($annotation instanceof Id) {
225
                    $propertyMetadata['identifier'] = true;
226
                } else {
227
                    if (!$propertyMetadata['public']) {
228
                        $propertyMetadata['setter'] = $this->guessSetter($class, $name);
229
                    }
230
                }
231
232
                if ($annotation instanceof Property) {
233
                    // we need the type (and possibly settings?) in Converter::denormalize()
234
                    $propertyMetadata['type'] = $annotation->type;
235
                    $propertyMetadata['settings'] = $annotation->settings;
236
                }
237
238
                if ($annotation instanceof Embedded) {
239
                    $propertyMetadata['embeded'] = true;
240
                    $propertyMetadata['class'] = $annotation->class;
241
                    $propertyMetadata['sub_properties'] = $this->getPropertyMetadata(
242
                        new \ReflectionClass($annotation->class),
243
                        true
244
                    );
245
                }
246
247
                $metadata[$annotation->getName() ?? Caser::snake($name)] = $propertyMetadata;
248
            }
249
        }
250
251
        return $metadata;
252
    }
253
254
    public function getAnalysisConfig(\ReflectionClass $class): array
255
    {
256
        $config = [];
257
        $mapping = $this->getClassMetadata($class);
258
259
        //Think how to remove these array merge
260
        $analyzers = $this->getListFromArrayByKey('analyzer', $mapping);
261
        $analyzers = array_merge($analyzers, $this->getListFromArrayByKey('search_analyzer', $mapping));
262
        $analyzers = array_merge($analyzers, $this->getListFromArrayByKey('search_quote_analyzer', $mapping));
263
264
        foreach ($analyzers as $analyzer) {
265
            if (isset($this->analysisConfig['analyzer'][$analyzer])) {
266
                $config['analyzer'][$analyzer] = $this->analysisConfig['analyzer'][$analyzer];
267
            }
268
        }
269
270
        foreach (['tokenizer', 'filter', 'normalizer', 'char_filter'] as $type) {
271
            $list = $this->getListFromArrayByKey($type, $config);
272
273
            foreach ($list as $listItem) {
274
                if (isset($this->analysisConfig[$type][$listItem])) {
275
                    $config[$type][$listItem] = $this->analysisConfig[$type][$listItem];
276
                }
277
            }
278
        }
279
280
        return $config;
281
    }
282
283
    protected function guessGetter(\ReflectionClass $class, $name): string
284
    {
285
        if ($class->hasMethod($name)) {
286
            return $name;
287
        }
288
289
        if ($class->hasMethod('get' . ucfirst($name))) {
290
            return 'get' . ucfirst($name);
291
        }
292
293
        if ($class->hasMethod('is' . ucfirst($name))) {
294
            return 'is' . ucfirst($name);
295
        }
296
297
        // if there are underscores in the name convert them to CamelCase
298
        if (strpos($name, '_')) {
299
            $name = Caser::camel($name);
300
            if ($class->hasMethod('get' . ucfirst($name))) {
301
                return 'get' . $name;
302
            }
303
            if ($class->hasMethod('is' . ucfirst($name))) {
304
                return 'is' . $name;
305
            }
306
        }
307
308
        throw new \Exception("Could not determine a getter for `$name` of class `{$class->getNamespaceName()}`");
309
    }
310
311
    protected function guessSetter(\ReflectionClass $class, $name): string
312
    {
313
        if ($class->hasMethod('set' . ucfirst($name))) {
314
            return 'set' . ucfirst($name);
315
        }
316
317
        // if there are underscores in the name convert them to CamelCase
318
        if (strpos($name, '_')) {
319
            $name = Caser::camel($name);
320
            if ($class->hasMethod('set' . ucfirst($name))) {
321
                return 'set' . $name;
322
            }
323
        }
324
325
        throw new \Exception("Could not determine a setter for `$name` of class `{$class->getNamespaceName()}`");
326
    }
327
328
    private function getListFromArrayByKey(string $searchKey, array $array): array
329
    {
330
        $list = [];
331
332
        foreach (new \RecursiveIteratorIterator(
333
            new \RecursiveArrayIterator($array),
334
            \RecursiveIteratorIterator::SELF_FIRST
335
        ) as $key => $value) {
336
            if ($key === $searchKey) {
337
                if (is_array($value)) {
338
                    $list = array_merge($list, $value);
339
                } else {
340
                    $list[] = $value;
341
                }
342
            }
343
        }
344
345
        return array_unique($list);
346
    }
347
348
    private function getObjectMappingType(\ReflectionClass $class): string
349
    {
350
        switch (true) {
351
            case $this->reader->getClassAnnotation($class, ObjectType::class):
352
                $type = ObjectType::TYPE;
353
                break;
354
            case $this->reader->getClassAnnotation($class, NestedType::class):
355
                $type = NestedType::TYPE;
356
                break;
357
            default:
358
                throw new \LogicException(
359
                    sprintf(
360
                        '%s must be used @ObjectType or @NestedType as embeddable object.',
361
                        $class->getName()
362
                    )
363
                );
364
        }
365
366
        return $type;
367
    }
368
369
    private function getDocumentPropertiesReflection(\ReflectionClass $class): array
370
    {
371
        if (in_array($class->getName(), $this->properties)) {
372
            return $this->properties[$class->getName()];
373
        }
374
375
        $properties = [];
376
377
        foreach ($class->getProperties() as $property) {
378
            if (!in_array($property->getName(), $properties)) {
379
                $properties[$property->getName()] = $property;
380
            }
381
        }
382
383
        $parentReflection = $class->getParentClass();
384
        if ($parentReflection !== false) {
385
            $properties = array_merge(
386
                $properties,
387
                array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties)
388
            );
389
        }
390
391
        $this->properties[$class->getName()] = $properties;
392
393
        return $properties;
394
    }
395
}
396