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\Document; |
17
|
|
|
use ONGR\ElasticsearchBundle\Annotation\Inherit; |
18
|
|
|
use ONGR\ElasticsearchBundle\Annotation\MultiField; |
19
|
|
|
use ONGR\ElasticsearchBundle\Annotation\Property; |
20
|
|
|
use ONGR\ElasticsearchBundle\Annotation\Skip; |
21
|
|
|
use ONGR\ElasticsearchBundle\Annotation\Suggester\AbstractSuggesterProperty; |
22
|
|
|
use ONGR\ElasticsearchBundle\Mapping\Proxy\ProxyFactory; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Document parser used for reading document annotations. |
26
|
|
|
*/ |
27
|
|
|
class DocumentParser |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* @const string |
31
|
|
|
*/ |
32
|
|
|
const SUGGESTER_PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Suggester\AbstractSuggesterProperty'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @const string |
36
|
|
|
*/ |
37
|
|
|
const PROPERTY_ANNOTATION = 'ONGR\ElasticsearchBundle\Annotation\Property'; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var Reader Used to read document annotations. |
41
|
|
|
*/ |
42
|
|
|
private $reader; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var DocumentFinder Used to find documents. |
46
|
|
|
*/ |
47
|
|
|
private $finder; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var array Contains gathered objects which later adds to documents. |
51
|
|
|
*/ |
52
|
|
|
private $objects = []; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var array Document properties aliases. |
56
|
|
|
*/ |
57
|
|
|
private $aliases = []; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var array Local cache for document properties. |
61
|
|
|
*/ |
62
|
|
|
private $properties = []; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param Reader $reader Used for reading annotations. |
66
|
|
|
* @param DocumentFinder $finder Used for resolving namespaces. |
67
|
|
|
*/ |
68
|
|
|
public function __construct(Reader $reader, DocumentFinder $finder) |
69
|
|
|
{ |
70
|
|
|
$this->reader = $reader; |
71
|
|
|
$this->finder = $finder; |
72
|
|
|
$this->registerAnnotations(); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Parses documents by used annotations and returns mapping for elasticsearch with some extra metadata. |
77
|
|
|
* |
78
|
|
|
* @param \ReflectionClass $reflectionClass |
79
|
|
|
* |
80
|
|
|
* @return array|null |
81
|
|
|
*/ |
82
|
|
|
public function parse(\ReflectionClass $reflectionClass) |
83
|
|
|
{ |
84
|
|
|
/** @var Document $class */ |
85
|
|
|
$class = $this |
86
|
|
|
->reader |
87
|
|
|
->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Document'); |
88
|
|
|
|
89
|
|
|
if ($class !== null && $class->create) { |
90
|
|
|
if ($class->parent !== null) { |
91
|
|
|
$parent = $this->getDocumentParentType( |
92
|
|
|
new \ReflectionClass($this->finder->getNamespace($class->parent)) |
93
|
|
|
); |
94
|
|
|
} else { |
95
|
|
|
$parent = null; |
96
|
|
|
} |
97
|
|
|
$type = $this->getDocumentType($reflectionClass, $class); |
98
|
|
|
$inherit = $this->getInheritedProperties($reflectionClass); |
99
|
|
|
|
100
|
|
|
$properties = $this->getProperties( |
101
|
|
|
$reflectionClass, |
102
|
|
|
array_merge($inherit, $this->getSkippedProperties($reflectionClass)) |
103
|
|
|
); |
104
|
|
|
|
105
|
|
|
if (!empty($inherit)) { |
106
|
|
|
$properties = array_merge( |
107
|
|
|
$properties, |
108
|
|
|
$this->getProperties($reflectionClass->getParentClass(), $inherit, true) |
109
|
|
|
); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return [ |
113
|
|
|
$type => [ |
114
|
|
|
'properties' => $properties, |
115
|
|
|
'fields' => array_merge( |
116
|
|
|
$class->dump(), |
117
|
|
|
['_parent' => $parent === null ? null : ['type' => $parent]] |
118
|
|
|
), |
119
|
|
|
'aliases' => $this->getAliases($reflectionClass), |
120
|
|
|
'objects' => $this->getObjects(), |
121
|
|
|
'proxyNamespace' => ProxyFactory::getProxyNamespace($reflectionClass, true), |
122
|
|
|
'namespace' => $reflectionClass->getName(), |
123
|
|
|
'class' => $reflectionClass->getShortName(), |
124
|
|
|
], |
125
|
|
|
]; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return null; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Returns property annotation data from reader. |
133
|
|
|
* |
134
|
|
|
* @param \ReflectionProperty $property |
135
|
|
|
* |
136
|
|
|
* @return AbstractSuggesterProperty|Property |
137
|
|
|
*/ |
138
|
|
|
public function getPropertyAnnotationData($property) |
139
|
|
|
{ |
140
|
|
|
$type = $this->reader->getPropertyAnnotation($property, self::PROPERTY_ANNOTATION); |
141
|
|
|
if ($type === null) { |
142
|
|
|
$type = $this->reader->getPropertyAnnotation($property, self::SUGGESTER_PROPERTY_ANNOTATION); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
return $type; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Returns objects used in document. |
150
|
|
|
* |
151
|
|
|
* @return array |
152
|
|
|
*/ |
153
|
|
|
private function getObjects() |
154
|
|
|
{ |
155
|
|
|
return array_keys($this->objects); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Finds aliases for every property used in document including parent classes. |
160
|
|
|
* |
161
|
|
|
* @param \ReflectionClass $reflectionClass |
162
|
|
|
* |
163
|
|
|
* @return array |
164
|
|
|
*/ |
165
|
|
|
private function getAliases(\ReflectionClass $reflectionClass) |
166
|
|
|
{ |
167
|
|
|
$reflectionName = $reflectionClass->getName(); |
168
|
|
|
if (array_key_exists($reflectionName, $this->aliases)) { |
169
|
|
|
return $this->aliases[$reflectionName]; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
$alias = []; |
173
|
|
|
/** @var \ReflectionProperty $property */ |
174
|
|
|
foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) { |
175
|
|
|
$type = $this->getPropertyAnnotationData($property); |
176
|
|
|
if ($type !== null) { |
177
|
|
|
$alias[$type->name] = [ |
178
|
|
|
'propertyName' => $name, |
179
|
|
|
'type' => $type->type, |
180
|
|
|
]; |
181
|
|
|
if ($type->objectName) { |
182
|
|
|
$child = new \ReflectionClass($this->finder->getNamespace($type->objectName)); |
183
|
|
|
$alias[$type->name] = array_merge( |
184
|
|
|
$alias[$type->name], |
185
|
|
|
[ |
186
|
|
|
'multiple' => $type instanceof Property ? $type->multiple : false, |
187
|
|
|
'aliases' => $this->getAliases($child), |
188
|
|
|
'proxyNamespace' => ProxyFactory::getProxyNamespace($child, true), |
189
|
|
|
'namespace' => $child->getName(), |
190
|
|
|
] |
191
|
|
|
); |
192
|
|
|
} |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$this->aliases[$reflectionName] = $alias; |
197
|
|
|
|
198
|
|
|
return $this->aliases[$reflectionName]; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Registers annotations to registry so that it could be used by reader. |
203
|
|
|
*/ |
204
|
|
|
private function registerAnnotations() |
205
|
|
|
{ |
206
|
|
|
$annotations = [ |
207
|
|
|
'Document', |
208
|
|
|
'Property', |
209
|
|
|
'Object', |
210
|
|
|
'Nested', |
211
|
|
|
'MultiField', |
212
|
|
|
'Inherit', |
213
|
|
|
'Skip', |
214
|
|
|
'Suggester/CompletionSuggesterProperty', |
215
|
|
|
'Suggester/ContextSuggesterProperty', |
216
|
|
|
'Suggester/Context/CategoryContext', |
217
|
|
|
'Suggester/Context/GeoLocationContext', |
218
|
|
|
]; |
219
|
|
|
|
220
|
|
|
foreach ($annotations as $annotation) { |
221
|
|
|
AnnotationRegistry::registerFile(__DIR__ . "/../Annotation/{$annotation}.php"); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Returns document parent. |
227
|
|
|
* |
228
|
|
|
* @param \ReflectionClass $reflectionClass |
229
|
|
|
* |
230
|
|
|
* @return string|null |
231
|
|
|
*/ |
232
|
|
View Code Duplication |
private function getDocumentParentType(\ReflectionClass $reflectionClass) |
|
|
|
|
233
|
|
|
{ |
234
|
|
|
/** @var Document $class */ |
235
|
|
|
$class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Document'); |
236
|
|
|
|
237
|
|
|
return $class ? $this->getDocumentType($reflectionClass, $class) : null; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @param \ReflectionClass $reflectionClass |
242
|
|
|
* |
243
|
|
|
* @return array |
244
|
|
|
*/ |
245
|
|
View Code Duplication |
private function getSkippedProperties(\ReflectionClass $reflectionClass) |
|
|
|
|
246
|
|
|
{ |
247
|
|
|
/** @var Skip $class */ |
248
|
|
|
$class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Skip'); |
249
|
|
|
|
250
|
|
|
return $class === null ? [] : $class->value; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* @param \ReflectionClass $reflectionClass |
255
|
|
|
* |
256
|
|
|
* @return array |
257
|
|
|
*/ |
258
|
|
View Code Duplication |
private function getInheritedProperties(\ReflectionClass $reflectionClass) |
|
|
|
|
259
|
|
|
{ |
260
|
|
|
/** @var Inherit $class */ |
261
|
|
|
$class = $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Inherit'); |
262
|
|
|
|
263
|
|
|
return $class === null ? [] : $class->value; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Returns document type. |
268
|
|
|
* |
269
|
|
|
* @param \ReflectionClass $reflectionClass |
270
|
|
|
* @param Document $document |
271
|
|
|
* |
272
|
|
|
* @return string |
273
|
|
|
*/ |
274
|
|
|
private function getDocumentType(\ReflectionClass $reflectionClass, Document $document) |
275
|
|
|
{ |
276
|
|
|
return empty($document->type) ? $reflectionClass->getShortName() : $document->type; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Returns all defined properties including private from parents. |
281
|
|
|
* |
282
|
|
|
* @param \ReflectionClass $reflectionClass |
283
|
|
|
* |
284
|
|
|
* @return array |
285
|
|
|
*/ |
286
|
|
|
private function getDocumentPropertiesReflection(\ReflectionClass $reflectionClass) |
287
|
|
|
{ |
288
|
|
|
if (in_array($reflectionClass->getName(), $this->properties)) { |
289
|
|
|
return $this->properties[$reflectionClass->getName()]; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
$properties = []; |
293
|
|
|
|
294
|
|
|
foreach ($reflectionClass->getProperties() as $property) { |
295
|
|
|
if (!in_array($property->getName(), $properties)) { |
296
|
|
|
$properties[$property->getName()] = $property; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
$parentReflection = $reflectionClass->getParentClass(); |
301
|
|
|
if ($parentReflection !== false) { |
302
|
|
|
$properties = array_merge( |
303
|
|
|
$properties, |
304
|
|
|
array_diff_key($this->getDocumentPropertiesReflection($parentReflection), $properties) |
305
|
|
|
); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$this->properties[$reflectionClass->getName()] = $properties; |
309
|
|
|
|
310
|
|
|
return $properties; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Returns properties of reflection class. |
315
|
|
|
* |
316
|
|
|
* @param \ReflectionClass $reflectionClass Class to read properties from. |
317
|
|
|
* @param array $properties Properties to skip. |
318
|
|
|
* @param bool $flag If false exludes properties, true only includes properties. |
319
|
|
|
* |
320
|
|
|
* @return array |
321
|
|
|
*/ |
322
|
|
|
private function getProperties(\ReflectionClass $reflectionClass, $properties = [], $flag = false) |
323
|
|
|
{ |
324
|
|
|
$mapping = []; |
325
|
|
|
/** @var \ReflectionProperty $property */ |
326
|
|
|
foreach ($this->getDocumentPropertiesReflection($reflectionClass) as $name => $property) { |
327
|
|
|
$type = $this->getPropertyAnnotationData($property); |
328
|
|
|
|
329
|
|
|
if ((in_array($name, $properties) && !$flag) |
330
|
|
|
|| (!in_array($name, $properties) && $flag) |
331
|
|
|
|| empty($type) |
332
|
|
|
) { |
333
|
|
|
continue; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
$maps = $type->dump(); |
337
|
|
|
|
338
|
|
|
// Object. |
339
|
|
|
if (in_array($type->type, ['object', 'nested']) && !empty($type->objectName)) { |
340
|
|
|
$maps = array_replace_recursive($maps, $this->getObjectMapping($type->objectName)); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
// MultiField. |
344
|
|
|
if (isset($maps['fields']) && !in_array($type->type, ['object', 'nested'])) { |
345
|
|
|
$fieldsMap = []; |
346
|
|
|
/** @var MultiField $field */ |
347
|
|
|
foreach ($maps['fields'] as $field) { |
348
|
|
|
$fieldsMap[$field->name] = $field->dump(); |
349
|
|
|
} |
350
|
|
|
$maps['fields'] = $fieldsMap; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
// Suggestions. |
354
|
|
|
if ($type instanceof AbstractSuggesterProperty) { |
355
|
|
|
$this->getObjectMapping($type->objectName); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
$mapping[$type->name] = $maps; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
return $mapping; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Returns object mapping. |
366
|
|
|
* |
367
|
|
|
* Loads from cache if it's already loaded. |
368
|
|
|
* |
369
|
|
|
* @param string $objectName |
370
|
|
|
* |
371
|
|
|
* @return array |
372
|
|
|
*/ |
373
|
|
|
private function getObjectMapping($objectName) |
374
|
|
|
{ |
375
|
|
|
$namespace = $this->finder->getNamespace($objectName); |
376
|
|
|
|
377
|
|
|
if (array_key_exists($namespace, $this->objects)) { |
378
|
|
|
return $this->objects[$namespace]; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
$this->objects[$namespace] = $this->getRelationMapping(new \ReflectionClass($namespace)); |
382
|
|
|
|
383
|
|
|
return $this->objects[$namespace]; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Returns relation mapping by its reflection. |
388
|
|
|
* |
389
|
|
|
* @param \ReflectionClass $reflectionClass |
390
|
|
|
* |
391
|
|
|
* @return array|null |
392
|
|
|
*/ |
393
|
|
|
private function getRelationMapping(\ReflectionClass $reflectionClass) |
394
|
|
|
{ |
395
|
|
|
if ($this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Object') |
396
|
|
|
|| $this->reader->getClassAnnotation($reflectionClass, 'ONGR\ElasticsearchBundle\Annotation\Nested') |
397
|
|
|
) { |
398
|
|
|
return ['properties' => $this->getProperties($reflectionClass)]; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
return null; |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
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.