Completed
Push — 6.0-dev ( 1ef812...ceea66 )
by Simonas
01:30
created

DocumentParser::getClassMetadata()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 8.0266
c 0
b 0
f 0
cc 7
nc 14
nop 1

How to fix   Long Method   

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 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($namespace): string
51
    {
52
        $class = new \ReflectionClass($namespace);
53
54
        /** @var Index $document */
55
        $document = $this->reader->getClassAnnotation($class, Index::class);
56
57
        return $document->alias ?? Caser::snake($class->getShortName());
58
    }
59
60
    public function isDefaultIndex($namespace): bool
61
    {
62
        $class = new \ReflectionClass($namespace);
63
64
        /** @var Index $document */
65
        $document = $this->reader->getClassAnnotation($class, Index::class);
66
67
        return $document->default;
68
    }
69
70
    /**
71
     * @deprecated will be deleted in v7. Types are deleted from elasticsearch.
72
     */
73
    public function getTypeName($namespace): string
74
    {
75
        $class = new \ReflectionClass($namespace);
76
77
        /** @var Index $document */
78
        $document = $this->reader->getClassAnnotation($class, Index::class);
79
80
        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...
81
    }
82
83
    public function getIndexMetadata($namespace): array
84
    {
85
        $class = new \ReflectionClass($namespace);
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($namespace);
100
101
        return array_filter(array_map('array_filter', [
102
            'settings' => $settings,
103
            'mappings' => [
104
                $this->getTypeName($namespace) => [
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(string $namespace): Index
125
    {
126
        /** @var Index $document */
127
        $document = $this->reader->getClassAnnotation(new \ReflectionClass($namespace), Index::class);
128
129
        return $document;
130
    }
131
132
    private function getClassMetadata(\ReflectionClass $reflectionClass): array
133
    {
134
        $mapping = [];
135
        $objFields = null;
136
        $arrayFields = null;
137
        $embeddedFields = null;
138
139
        /** @var \ReflectionProperty $property */
140
        foreach ($this->getDocumentPropertiesReflection($reflectionClass) 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[$reflectionClass->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[$reflectionClass->getName()] = $arrayFields;
180
        $this->cache->save(self::ARRAY_CACHED_FIELDS, $cacheItem);
181
182
        $cacheItem = $this->cache->fetch(self::OBJ_CACHED_FIELDS) ?? [];
183
        $cacheItem[$reflectionClass->getName()] = $objFields;
184
        $this->cache->save(self::OBJ_CACHED_FIELDS, $cacheItem);
185
186
        return $mapping;
187
    }
188
189
    public function getAnalysisConfig($namespace): array
190
    {
191
        $config = [];
192
        $mapping = $this->getClassMetadata(new \ReflectionClass($namespace));
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 $reflectionClass): string
239
    {
240
        switch (true) {
241
            case $this->reader->getClassAnnotation($reflectionClass, ObjectType::class):
242
                $type = ObjectType::TYPE;
243
                break;
244
            case $this->reader->getClassAnnotation($reflectionClass, NestedType::class):
245
                $type = NestedType::TYPE;
246
                break;
247
            default:
248
                throw new \LogicException(
249
                    sprintf(
250
                        '%s should have @ObjectType or @NestedType annotation to be used as embeddable object.',
251
                        $reflectionClass->getName()
252
                    )
253
                );
254
        }
255
256
        return $type;
257
    }
258
259
    private function getDocumentPropertiesReflection(\ReflectionClass $reflectionClass): array
260
    {
261
        if (in_array($reflectionClass->getName(), $this->properties)) {
262
            return $this->properties[$reflectionClass->getName()];
263
        }
264
265
        $properties = [];
266
267
        foreach ($reflectionClass->getProperties() as $property) {
268
            if (!in_array($property->getName(), $properties)) {
269
                $properties[$property->getName()] = $property;
270
            }
271
        }
272
273
        $parentReflection = $reflectionClass->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[$reflectionClass->getName()] = $properties;
282
283
        return $properties;
284
    }
285
}
286