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'); |
|
|
|
|
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'; |
|
|
|
|
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) => [ |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|
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.