Completed
Pull Request — 6.0-dev (#875)
by Simonas
08:47
created

DocumentParser   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 8
dl 0
loc 257
c 0
b 0
f 0
rs 9.44

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getDocumentNamespace() 0 12 3
A getParsedDocument() 0 7 1
B getClassMetadata() 0 56 7
B getAnalysisConfig() 0 28 6
A getListFromArrayByKey() 0 19 4
A getObjectMappingType() 0 20 3
A getDocumentPropertiesReflection() 0 26 5
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
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\Index;
20
use ONGR\ElasticsearchBundle\Annotation\NestedType;
21
use ONGR\ElasticsearchBundle\Annotation\ObjectType;
22
use ONGR\ElasticsearchBundle\Annotation\PropertiesAwareInterface;
23
use ONGR\ElasticsearchBundle\Annotation\Property;
24
use ONGR\ElasticsearchBundle\DependencyInjection\Configuration;
25
26
/**
27
 * Document parser used for reading document annotations.
28
 */
29
class DocumentParser
30
{
31
    const OBJ_CACHED_FIELDS = 'ongr.obj_fields';
32
    const EMBEDDED_CACHED_FIELDS = 'ongr.embedded_fields';
33
    const ARRAY_CACHED_FIELDS = 'ongr.array_fields';
34
35
    private $reader;
36
    private $properties = [];
37
    private $analysisConfig = [];
38
    private $cache;
39
40
    public function __construct(Reader $reader, Cache $cache, array $analysisConfig = [])
41
    {
42
        $this->reader = $reader;
43
        $this->cache = $cache;
44
        $this->analysisConfig = $analysisConfig;
45
46
        #Fix for annotations loader until doctrine/annotations 2.0 will be released with the full autoload support.
47
        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...
48
    }
49
50
    public function getIndexAliasName(\ReflectionClass $class): string
51
    {
52
        /** @var Index $document */
53
        $document = $this->reader->getClassAnnotation($class, Index::class);
54
55
        return $document->alias ?? Caser::snake($class->getShortName());
56
    }
57
58
    public function isDefaultIndex(\ReflectionClass $class): bool
59
    {
60
        /** @var Index $document */
61
        $document = $this->reader->getClassAnnotation($class, Index::class);
62
63
        return $document->default;
64
    }
65
66
    public function getIndexAnnotation(\ReflectionClass $class)
67
    {
68
        /** @var Index $document */
69
        $document = $this->reader->getClassAnnotation($class, Index::class);
70
71
        return $document;
72
    }
73
74
    /**
75
     * @deprecated will be deleted in v7. Types are deleted from elasticsearch.
76
     */
77
    public function getTypeName(\ReflectionClass $class): string
78
    {
79
        /** @var Index $document */
80
        $document = $this->reader->getClassAnnotation($class, Index::class);
81
82
        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...
83
    }
84
85
    public function getIndexMetadata(\ReflectionClass $class): array
86
    {
87
        if ($class->isTrait()) {
88
            return [];
89
        }
90
91
        /** @var Index $document */
92
        $document = $this->reader->getClassAnnotation($class, Index::class);
93
94
        if ($document === null) {
95
            return [];
96
        }
97
98
        $settings = $document->getSettings();
99
        $settings['analysis'] = $this->getAnalysisConfig($class);
100
101
        return array_filter(array_map('array_filter', [
102
            'settings' => $settings,
103
            'mappings' => [
104
                $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...
105
                    'properties' => array_filter($this->getClassMetadata($class))
106
                ]
107
            ]
108
        ]));
109
    }
110
111
    public function getDocumentNamespace(string $indexAlias): ?string
112
    {
113
        if ($this->cache->contains(Configuration::ONGR_INDEXES)) {
114
            $indexes = $this->cache->fetch(Configuration::ONGR_INDEXES);
115
116
            if (isset($indexes[$indexAlias])) {
117
                return $indexes[$indexAlias];
118
            }
119
        }
120
121
        return null;
122
    }
123
124
    public function getParsedDocument(\ReflectionClass $class): Index
125
    {
126
        /** @var Index $document */
127
        $document = $this->reader->getClassAnnotation($class, Index::class);
128
129
        return $document;
130
    }
131
132
    private function getClassMetadata(\ReflectionClass $class): array
133
    {
134
        $mapping = [];
135
        $objFields = null;
136
        $arrayFields = null;
137
        $embeddedFields = null;
138
139
        /** @var \ReflectionProperty $property */
140
        foreach ($this->getDocumentPropertiesReflection($class) as $name => $property) {
141
            $annotations = $this->reader->getPropertyAnnotations($property);
142
143
            /** @var AbstractAnnotation $annotation */
144
            foreach ($annotations as $annotation) {
145
                if (!$annotation instanceof PropertiesAwareInterface) {
146
                    continue;
147
                }
148
149
                $fieldMapping = $annotation->getSettings();
150
151
                if ($annotation instanceof Property) {
152
                    $fieldMapping['type'] = $annotation->type;
153
                    $fieldMapping['analyzer'] = $annotation->analyzer;
154
                    $fieldMapping['search_analyzer'] = $annotation->searchAnalyzer;
155
                    $fieldMapping['search_quote_analyzer'] = $annotation->searchQuoteAnalyzer;
156
                }
157
158
                if ($annotation instanceof Embedded) {
159
                    $embeddedClass = new \ReflectionClass($annotation->class);
160
                    $fieldMapping['type'] = $this->getObjectMappingType($embeddedClass);
161
                    $fieldMapping['properties'] = $this->getClassMetadata($embeddedClass);
162
                    $embeddedFields[$name] = $annotation->class;
163
                }
164
165
                $mapping[$annotation->getName() ?? Caser::snake($name)] = array_filter($fieldMapping);
166
                $objFields[$name] = $annotation->getName() ?? Caser::snake($name);
167
                $arrayFields[$annotation->getName() ?? Caser::snake($name)] = $name;
168
            }
169
        }
170
171
        //Embeded fields are option compared to the array or object mapping.
172
        if ($embeddedFields) {
173
            $cacheItem = $this->cache->fetch(self::EMBEDDED_CACHED_FIELDS) ?? [];
174
            $cacheItem[$class->getName()] = $embeddedFields;
175
            $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...
176
        }
177
178
        $cacheItem = $this->cache->fetch(self::ARRAY_CACHED_FIELDS) ?? [];
179
        $cacheItem[$class->getName()] = $arrayFields;
180
        $this->cache->save(self::ARRAY_CACHED_FIELDS, $cacheItem);
181
182
        $cacheItem = $this->cache->fetch(self::OBJ_CACHED_FIELDS) ?? [];
183
        $cacheItem[$class->getName()] = $objFields;
184
        $this->cache->save(self::OBJ_CACHED_FIELDS, $cacheItem);
185
186
        return $mapping;
187
    }
188
189
    public function getAnalysisConfig(\ReflectionClass $class): array
190
    {
191
        $config = [];
192
        $mapping = $this->getClassMetadata($class);
193
194
        //Think how to remove these array merge
195
        $analyzers = $this->getListFromArrayByKey('analyzer', $mapping);
196
        $analyzers = array_merge($analyzers, $this->getListFromArrayByKey('search_analyzer', $mapping));
197
        $analyzers = array_merge($analyzers, $this->getListFromArrayByKey('search_quote_analyzer', $mapping));
198
199
        foreach ($analyzers as $analyzer) {
200
            if (isset($this->analysisConfig['analyzer'][$analyzer])) {
201
                $config['analyzer'][$analyzer] = $this->analysisConfig['analyzer'][$analyzer];
202
            }
203
        }
204
205
        foreach (['tokenizer', 'filter', 'normalizer', 'char_filter'] as $type) {
206
            $list = $this->getListFromArrayByKey($type, $config);
207
208
            foreach ($list as $listItem) {
209
                if (isset($this->analysisConfig[$type][$listItem])) {
210
                    $config[$type][$listItem] = $this->analysisConfig[$type][$listItem];
211
                }
212
            }
213
        }
214
215
        return $config;
216
    }
217
218
    private function getListFromArrayByKey(string $searchKey, array $array): array
219
    {
220
        $list = [];
221
222
        foreach (new \RecursiveIteratorIterator(
223
            new \RecursiveArrayIterator($array),
224
            \RecursiveIteratorIterator::SELF_FIRST
225
        ) as $key => $value) {
226
            if ($key === $searchKey) {
227
                if (is_array($value)) {
228
                    $list = array_merge($list, $value);
229
                } else {
230
                    $list[] = $value;
231
                }
232
            }
233
        }
234
235
        return array_unique($list);
236
    }
237
238
    private function getObjectMappingType(\ReflectionClass $class): string
239
    {
240
        switch (true) {
241
            case $this->reader->getClassAnnotation($class, ObjectType::class):
242
                $type = ObjectType::TYPE;
243
                break;
244
            case $this->reader->getClassAnnotation($class, NestedType::class):
245
                $type = NestedType::TYPE;
246
                break;
247
            default:
248
                throw new \LogicException(
249
                    sprintf(
250
                        '%s must be used @ObjectType or @NestedType as embeddable object.',
251
                        $class->getName()
252
                    )
253
                );
254
        }
255
256
        return $type;
257
    }
258
259
    private function getDocumentPropertiesReflection(\ReflectionClass $class): array
260
    {
261
        if (in_array($class->getName(), $this->properties)) {
262
            return $this->properties[$class->getName()];
263
        }
264
265
        $properties = [];
266
267
        foreach ($class->getProperties() as $property) {
268
            if (!in_array($property->getName(), $properties)) {
269
                $properties[$property->getName()] = $property;
270
            }
271
        }
272
273
        $parentReflection = $class->getParentClass();
274
        if ($parentReflection !== false) {
275
            $properties = array_merge(
276
                $properties,
277
                array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties)
278
            );
279
        }
280
281
        $this->properties[$class->getName()] = $properties;
282
283
        return $properties;
284
    }
285
}
286