1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace As3\Modlr\Persister\MongoDb; |
4
|
|
|
|
5
|
|
|
use \Closure; |
6
|
|
|
use \MongoId; |
7
|
|
|
use As3\Modlr\Metadata\AttributeMetadata; |
8
|
|
|
use As3\Modlr\Metadata\EmbeddedPropMetadata; |
9
|
|
|
use As3\Modlr\Metadata\EntityMetadata; |
10
|
|
|
use As3\Modlr\Metadata\RelationshipMetadata; |
11
|
|
|
use As3\Modlr\Models\Embed; |
12
|
|
|
use As3\Modlr\Models\Model; |
13
|
|
|
use As3\Modlr\Persister\PersisterException; |
14
|
|
|
use As3\Modlr\Store\Store; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Handles persistence formatting operations for MongoDB. |
18
|
|
|
* - Formats query criteria to proper keys and values. |
19
|
|
|
* - Formats attribute, relationship, and embed values for insertion to the db. |
20
|
|
|
* - Formats identifier values for insertion to the db. |
21
|
|
|
* |
22
|
|
|
* @author Jacob Bare <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
final class Formatter |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* Query operators. |
28
|
|
|
* Organized into handling groups. |
29
|
|
|
* |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
private $ops = [ |
33
|
|
|
'root' => ['$and', '$or', '$nor'], |
34
|
|
|
'single' => ['$eq', '$gt', '$gte', '$lt', '$lte', '$ne'], |
35
|
|
|
'multiple' => ['$in', '$nin', '$all'], |
36
|
|
|
'recursive' => ['$not', '$elemMatch'], |
37
|
|
|
'ignore' => ['$exists', '$type', '$mod', '$size', '$regex', '$text', '$where'], |
38
|
|
|
]; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Formats a set of query criteria for a Model. |
42
|
|
|
* Ensures the id and type fields are properly applied. |
43
|
|
|
* Ensures that values are properly converted to their database equivalents: e.g dates, mongo ids, etc. |
44
|
|
|
* |
45
|
|
|
* @param EntityMetadata $metadata |
46
|
|
|
* @param Store $store |
47
|
|
|
* @param array $criteria |
48
|
|
|
* @return array |
49
|
|
|
*/ |
50
|
|
|
public function formatQuery(EntityMetadata $metadata, Store $store, array $criteria) |
51
|
|
|
{ |
52
|
|
|
$formatted = []; |
53
|
|
|
foreach ($criteria as $key => $value) { |
54
|
|
|
|
55
|
|
|
if ($this->isOpType('root', $key) && is_array($value)) { |
56
|
|
|
foreach ($value as $subKey => $subValue) { |
57
|
|
|
$formatted[$key][$subKey] = $this->formatQuery($metadata, $store, $subValue); |
58
|
|
|
} |
59
|
|
|
continue; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
if ($this->isOperator($key) && is_array($value)) { |
63
|
|
|
$formatted[$key] = $this->formatQuery($metadata, $store, $value); |
64
|
|
|
continue; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
list($key, $value) = $this->formatQueryElement($key, $value, $metadata, $store); |
68
|
|
|
$formatted[$key] = $value; |
69
|
|
|
} |
70
|
|
|
return $formatted; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Prepares and formats an attribute value for proper insertion into the database. |
75
|
|
|
* |
76
|
|
|
* @param AttributeMetadata $attrMeta |
77
|
|
|
* @param mixed $value |
78
|
|
|
* @return mixed |
79
|
|
|
*/ |
80
|
|
|
public function getAttributeDbValue(AttributeMetadata $attrMeta, $value) |
81
|
|
|
{ |
82
|
|
|
// Handle data type conversion, if needed. |
83
|
|
|
if ('date' === $attrMeta->dataType && $value instanceof \DateTime) { |
84
|
|
|
return new \MongoDate($value->getTimestamp(), $value->format('u')); |
85
|
|
|
} |
86
|
|
|
if ('array' === $attrMeta->dataType && empty($value)) { |
87
|
|
|
return; |
88
|
|
|
} |
89
|
|
|
if ('object' === $attrMeta->dataType) { |
90
|
|
|
$array = (array) $value; |
91
|
|
|
return empty($array) ? null : $value; |
92
|
|
|
} |
93
|
|
|
return $value; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Prepares and formats a has-many embed for proper insertion into the database. |
98
|
|
|
* |
99
|
|
|
* @param EmbeddedPropMetadata $embeddedPropMeta |
100
|
|
|
* @param array|null $embeds |
101
|
|
|
* @return mixed |
102
|
|
|
*/ |
103
|
|
|
public function getEmbedManyDbValue(EmbeddedPropMetadata $embeddedPropMeta, array $embeds = null) |
104
|
|
|
{ |
105
|
|
|
if (null === $embeds) { |
106
|
|
|
return; |
107
|
|
|
} |
108
|
|
|
$created = []; |
109
|
|
|
foreach ($embeds as $embed) { |
110
|
|
|
$created[] = $this->createEmbed($embeddedPropMeta, $embed); |
111
|
|
|
} |
112
|
|
|
return empty($created) ? null : $created; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Prepares and formats a has-one embed for proper insertion into the database. |
117
|
|
|
* |
118
|
|
|
* @param EmbeddedPropMetadata $embeddedPropMeta |
119
|
|
|
* @param Embed|null $embed |
120
|
|
|
* @return mixed |
121
|
|
|
*/ |
122
|
|
|
public function getEmbedOneDbValue(EmbeddedPropMetadata $embeddedPropMeta, Embed $embed = null) |
123
|
|
|
{ |
124
|
|
|
if (null === $embed) { |
125
|
|
|
return; |
126
|
|
|
} |
127
|
|
|
return $this->createEmbed($embeddedPropMeta, $embed); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Prepares and formats a has-one relationship model for proper insertion into the database. |
132
|
|
|
* |
133
|
|
|
* @param RelationshipMetadata $relMeta |
134
|
|
|
* @param Model|null $model |
135
|
|
|
* @return mixed |
136
|
|
|
*/ |
137
|
|
|
public function getHasOneDbValue(RelationshipMetadata $relMeta, Model $model = null) |
138
|
|
|
{ |
139
|
|
|
if (null === $model || true === $relMeta->isInverse) { |
140
|
|
|
return; |
141
|
|
|
} |
142
|
|
|
return $this->createReference($relMeta, $model); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Prepares and formats a has-many relationship model set for proper insertion into the database. |
147
|
|
|
* |
148
|
|
|
* @param RelationshipMetadata $relMeta |
149
|
|
|
* @param Model[]|null $models |
150
|
|
|
* @return mixed |
151
|
|
|
*/ |
152
|
|
|
public function getHasManyDbValue(RelationshipMetadata $relMeta, array $models = null) |
153
|
|
|
{ |
154
|
|
|
if (null === $models || true === $relMeta->isInverse) { |
155
|
|
|
return null; |
156
|
|
|
} |
157
|
|
|
$references = []; |
158
|
|
|
foreach ($models as $model) { |
159
|
|
|
$references[] = $this->createReference($relMeta, $model); |
160
|
|
|
} |
161
|
|
|
return empty($references) ? null : $references; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* {@inheritDoc} |
166
|
|
|
*/ |
167
|
|
|
public function getIdentifierDbValue($identifier, $strategy = null) |
168
|
|
|
{ |
169
|
|
|
if (false === $this->isIdStrategySupported($strategy)) { |
170
|
|
|
throw PersisterException::nyi('ID conversion currently only supports an object strategy, or none at all.'); |
171
|
|
|
} |
172
|
|
|
if ($identifier instanceof MongoId) { |
173
|
|
|
return $identifier; |
174
|
|
|
} |
175
|
|
|
return new MongoId($identifier); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Gets all possible identifier field keys (internal and persistence layer). |
180
|
|
|
* |
181
|
|
|
* @return array |
182
|
|
|
*/ |
183
|
|
|
public function getIdentifierFields() |
184
|
|
|
{ |
185
|
|
|
return [Persister::IDENTIFIER_KEY, EntityMetadata::ID_KEY]; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Gets all possible model type keys (internal and persistence layer). |
190
|
|
|
* |
191
|
|
|
* @return array |
192
|
|
|
*/ |
193
|
|
|
public function getTypeFields() |
194
|
|
|
{ |
195
|
|
|
return [Persister::POLYMORPHIC_KEY, EntityMetadata::TYPE_KEY]; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Determines if a field key is an identifier. |
200
|
|
|
* Uses both the internal and persistence identifier keys. |
201
|
|
|
* |
202
|
|
|
* @param string $key |
203
|
|
|
* @return bool |
204
|
|
|
*/ |
205
|
|
|
public function isIdentifierField($key) |
206
|
|
|
{ |
207
|
|
|
return in_array($key, $this->getIdentifierFields()); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Determines if the provided id strategy is supported. |
212
|
|
|
* |
213
|
|
|
* @param string|null $strategy |
214
|
|
|
* @return bool |
215
|
|
|
*/ |
216
|
|
|
public function isIdStrategySupported($strategy) |
217
|
|
|
{ |
218
|
|
|
return (null === $strategy || 'object' === $strategy); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Determines if a field key is a model type field. |
223
|
|
|
* Uses both the internal and persistence model type keys. |
224
|
|
|
* |
225
|
|
|
* @param string $key |
226
|
|
|
* @return bool |
227
|
|
|
*/ |
228
|
|
|
public function isTypeField($key) |
229
|
|
|
{ |
230
|
|
|
return in_array($key, $this->getTypeFields()); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Creates an embed for storage of an embed model in the database |
235
|
|
|
* |
236
|
|
|
* @param EmbeddedPropMetadata $embeddedPropMeta |
237
|
|
|
* @param Embed $embed |
238
|
|
|
* @return array|null |
239
|
|
|
*/ |
240
|
|
|
private function createEmbed(EmbeddedPropMetadata $embeddedPropMeta, Embed $embed) |
241
|
|
|
{ |
242
|
|
|
$embedMeta = $embeddedPropMeta->embedMeta; |
243
|
|
|
|
244
|
|
|
$obj = []; |
245
|
|
|
foreach ($embedMeta->getAttributes() as $key => $attrMeta) { |
246
|
|
|
$value = $this->getAttributeDbValue($attrMeta, $embed->get($key)); |
247
|
|
|
if (null === $value) { |
248
|
|
|
continue; |
249
|
|
|
} |
250
|
|
|
$obj[$key] = $value; |
251
|
|
|
} |
252
|
|
|
foreach ($embedMeta->getEmbeds() as $key => $propMeta) { |
253
|
|
|
$value = (true === $propMeta->isOne()) ? $this->getEmbedOneDbValue($propMeta, $embed->get($key)) : $this->getEmbedManyDbValue($propMeta, $embed->get($key)); |
254
|
|
|
if (null === $value) { |
255
|
|
|
continue; |
256
|
|
|
} |
257
|
|
|
$obj[$key] = $value; |
258
|
|
|
} |
259
|
|
|
return $obj; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Creates a reference for storage of a related model in the database |
264
|
|
|
* |
265
|
|
|
* @param RelationshipMetadata $relMeta |
266
|
|
|
* @param Model $model |
267
|
|
|
* @return mixed |
268
|
|
|
*/ |
269
|
|
|
private function createReference(RelationshipMetadata $relMeta, Model $model) |
270
|
|
|
{ |
271
|
|
|
$reference = []; |
272
|
|
|
$identifier = $this->getIdentifierDbValue($model->getId()); |
273
|
|
|
if (true === $relMeta->isPolymorphic()) { |
274
|
|
|
$reference[Persister::IDENTIFIER_KEY] = $identifier; |
275
|
|
|
$reference[Persister::TYPE_KEY] = $model->getType(); |
276
|
|
|
return $reference; |
277
|
|
|
} |
278
|
|
|
return $identifier; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Formats a query element and ensures the correct key and value are set. |
283
|
|
|
* Returns a tuple of the formatted key and value. |
284
|
|
|
* |
285
|
|
|
* @param string $key |
286
|
|
|
* @param mixed $value |
287
|
|
|
* @param EntityMetadata $metadata |
288
|
|
|
* @param Store $store |
289
|
|
|
* @return array |
290
|
|
|
*/ |
291
|
|
|
private function formatQueryElement($key, $value, EntityMetadata $metadata, Store $store) |
292
|
|
|
{ |
293
|
|
|
// Handle root fields: id or model type. |
294
|
|
|
if (null !== $result = $this->formatQueryElementRoot($key, $value, $metadata)) { |
295
|
|
|
return $result; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
// Handle attributes. |
299
|
|
|
if (null !== $result = $this->formatQueryElementAttr($key, $value, $metadata, $store)) { |
300
|
|
|
return $result; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
// Handle relationships. |
304
|
|
|
if (null !== $result = $this->formatQueryElementRel($key, $value, $metadata, $store)) { |
305
|
|
|
return $result; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
// Handle dot notated fields. |
309
|
|
|
if (null !== $result = $this->formatQueryElementDotted($key, $value, $metadata, $store)) { |
310
|
|
|
return $result; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
// Pass remaining elements unconverted. |
314
|
|
|
return [$key, $value]; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Formats an attribute query element. |
319
|
|
|
* Returns a tuple of the formatted key and value, or null if the key is not an attribute field. |
320
|
|
|
* |
321
|
|
|
* @param string $key |
322
|
|
|
* @param mixed $value |
323
|
|
|
* @param EntityMetadata $metadata |
324
|
|
|
* @param Store $store |
325
|
|
|
* @return array|null |
326
|
|
|
*/ |
327
|
|
|
private function formatQueryElementAttr($key, $value, EntityMetadata $metadata, Store $store) |
328
|
|
|
{ |
329
|
|
|
if (null === $attrMeta = $metadata->getAttribute($key)) { |
330
|
|
|
return; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
$converter = $this->getQueryAttrConverter($store, $attrMeta); |
334
|
|
|
|
335
|
|
|
if (is_array($value)) { |
336
|
|
|
|
337
|
|
|
if (true === $this->hasOperators($value)) { |
338
|
|
|
return [$key, $this->formatQueryExpression($value, $converter)]; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
if (in_array($attrMeta->dataType, ['array', 'object'])) { |
342
|
|
|
return [$key, $value]; |
343
|
|
|
} |
344
|
|
|
return [$key, $this->formatQueryExpression(['$in' => $value], $converter)]; |
345
|
|
|
} |
346
|
|
|
return [$key, $converter($value)]; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Formats a dot-notated field. |
351
|
|
|
* Returns a tuple of the formatted key and value, or null if the key is not a dot-notated field, or cannot be handled. |
352
|
|
|
* |
353
|
|
|
* @param string $key |
354
|
|
|
* @param mixed $value |
355
|
|
|
* @param EntityMetadata $metadata |
356
|
|
|
* @param Store $store |
357
|
|
|
* @return array|null |
358
|
|
|
*/ |
359
|
|
|
private function formatQueryElementDotted($key, $value, EntityMetadata $metadata, Store $store) |
360
|
|
|
{ |
361
|
|
|
if (false === stripos($key, '.')) { |
362
|
|
|
return; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$parts = explode('.', $key); |
366
|
|
|
$root = array_shift($parts); |
367
|
|
|
if (false === $metadata->hasRelationship($root)) { |
368
|
|
|
// Nothing to format. Allow the dotted field to pass normally. |
369
|
|
|
return [$key, $value]; |
370
|
|
|
} |
371
|
|
|
$hasIndex = is_numeric($parts[0]); |
372
|
|
|
|
373
|
|
|
if (true === $hasIndex) { |
374
|
|
|
$subKey = isset($parts[1]) ? $parts[1] : 'id'; |
375
|
|
|
} else { |
376
|
|
|
$subKey = $parts[0]; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
View Code Duplication |
if ($this->isIdentifierField($subKey)) { |
|
|
|
|
380
|
|
|
// Handle like a regular relationship |
381
|
|
|
list($key, $value) = $this->formatQueryElementRel($root, $value, $metadata, $store); |
382
|
|
|
$key = (true === $hasIndex) ? sprintf('%s.%s', $key, $parts[0]) : $key; |
383
|
|
|
return [$key, $value]; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
View Code Duplication |
if ($this->isTypeField($subKey)) { |
|
|
|
|
387
|
|
|
// Handle as a model type field. |
388
|
|
|
list($key, $value) = $this->formatQueryElementRoot($subKey, $value, $metadata); |
389
|
|
|
$key = (true === $hasIndex) ? sprintf('%s.%s.%s', $root, $parts[0], $key) : sprintf('%s.%s', $root, $key); |
390
|
|
|
return [$key, $value]; |
391
|
|
|
} |
392
|
|
|
return [$key, $value]; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Formats a relationship query element. |
397
|
|
|
* Returns a tuple of the formatted key and value, or null if the key is not a relationship field. |
398
|
|
|
* |
399
|
|
|
* @param string $key |
400
|
|
|
* @param mixed $value |
401
|
|
|
* @param EntityMetadata $metadata |
402
|
|
|
* @param Store $store |
403
|
|
|
* @return array|null |
404
|
|
|
*/ |
405
|
|
|
private function formatQueryElementRel($key, $value, EntityMetadata $metadata, Store $store) |
406
|
|
|
{ |
407
|
|
|
if (null === $relMeta = $metadata->getRelationship($key)) { |
408
|
|
|
return; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
$converter = $this->getQueryRelConverter($store, $relMeta); |
412
|
|
|
|
413
|
|
|
if (true === $relMeta->isPolymorphic()) { |
414
|
|
|
$key = sprintf('%s.%s', $key, Persister::IDENTIFIER_KEY); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
View Code Duplication |
if (is_array($value)) { |
|
|
|
|
418
|
|
|
$value = (true === $this->hasOperators($value)) ? $value : ['$in' => $value]; |
419
|
|
|
return [$key, $this->formatQueryExpression($value, $converter)]; |
420
|
|
|
} |
421
|
|
|
return [$key, $converter($value)]; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Formats a root query element: either id or model type. |
427
|
|
|
* Returns a tuple of the formatted key and value, or null if the key is not a root field. |
428
|
|
|
* |
429
|
|
|
* @param string $key |
430
|
|
|
* @param mixed $value |
431
|
|
|
* @param EntityMetadata $metadata |
432
|
|
|
* @return array|null |
433
|
|
|
*/ |
434
|
|
|
private function formatQueryElementRoot($key, $value, EntityMetadata $metadata) |
435
|
|
|
{ |
436
|
|
|
if (true === $this->isIdentifierField($key)) { |
437
|
|
|
$dbKey = Persister::IDENTIFIER_KEY; |
438
|
|
|
} elseif (true === $this->isTypeField($key)) { |
439
|
|
|
$dbKey = Persister::POLYMORPHIC_KEY; |
440
|
|
|
} else { |
441
|
|
|
return; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
$converter = $this->getQueryRootConverter($metadata, $dbKey); |
445
|
|
View Code Duplication |
if (is_array($value)) { |
|
|
|
|
446
|
|
|
$value = (true === $this->hasOperators($value)) ? $value : ['$in' => $value]; |
447
|
|
|
return [$dbKey, $this->formatQueryExpression($value, $converter)]; |
448
|
|
|
} |
449
|
|
|
return [$dbKey, $converter($value)]; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Formats a query expression. |
454
|
|
|
* |
455
|
|
|
* @param array $expression |
456
|
|
|
* @param Closure $converter |
457
|
|
|
* @return array |
458
|
|
|
*/ |
459
|
|
|
private function formatQueryExpression(array $expression, Closure $converter) |
460
|
|
|
{ |
461
|
|
|
foreach ($expression as $key => $value) { |
462
|
|
|
|
463
|
|
|
if ('$regex' === $key && !$value instanceof \MongoRegex) { |
464
|
|
|
$expression[$key] = new \MongoRegex($value); |
465
|
|
|
continue; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
if (true === $this->isOpType('ignore', $key)) { |
469
|
|
|
continue; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
if (true === $this->isOpType('single', $key)) { |
473
|
|
|
$expression[$key] = $converter($value); |
474
|
|
|
continue; |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
if (true === $this->isOpType('multiple', $key)) { |
478
|
|
|
$value = (array) $value; |
479
|
|
|
foreach ($value as $subKey => $subValue) { |
480
|
|
|
$expression[$key][$subKey] = $converter($subValue); |
481
|
|
|
} |
482
|
|
|
continue; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
if (true === $this->isOpType('recursive', $key)) { |
486
|
|
|
$value = (array) $value; |
487
|
|
|
$expression[$key] = $this->formatQueryExpression($value, $converter); |
488
|
|
|
continue; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
$expression[$key] = $converter($value); |
492
|
|
|
} |
493
|
|
|
return $expression; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Gets the converter for handling attribute values in queries. |
498
|
|
|
* |
499
|
|
|
* @param Store $store |
500
|
|
|
* @param AttributeMetadata $attrMeta |
501
|
|
|
* @return Closure |
502
|
|
|
*/ |
503
|
|
|
private function getQueryAttrConverter(Store $store, AttributeMetadata $attrMeta) |
504
|
|
|
{ |
505
|
|
|
return function ($value) use ($store, $attrMeta) { |
506
|
|
|
if (in_array($attrMeta->dataType, ['object', 'array'])) { |
507
|
|
|
// Leave the value as is. |
508
|
|
|
return $value; |
509
|
|
|
} |
510
|
|
|
$value = $store->convertAttributeValue($attrMeta->dataType, $value); |
511
|
|
|
return $this->getAttributeDbValue($attrMeta, $value); |
512
|
|
|
}; |
513
|
|
|
|
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Gets the converter for handling relationship values in queries. |
518
|
|
|
* |
519
|
|
|
* @param Store $store |
520
|
|
|
* @param RelationshipMetadata $relMeta |
521
|
|
|
* @return Closure |
522
|
|
|
*/ |
523
|
|
|
private function getQueryRelConverter(Store $store, RelationshipMetadata $relMeta) |
524
|
|
|
{ |
525
|
|
|
$related = $store->getMetadataForType($relMeta->getEntityType()); |
526
|
|
|
return $this->getQueryRootConverter($related, Persister::IDENTIFIER_KEY); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* Gets the converter for handling root field values in queries (id or model type). |
531
|
|
|
* |
532
|
|
|
* @param EntityMetadata $metadata |
533
|
|
|
* @param string $key |
534
|
|
|
* @return Closure |
535
|
|
|
*/ |
536
|
|
|
private function getQueryRootConverter(EntityMetadata $metadata, $key) |
537
|
|
|
{ |
538
|
|
|
return function($value) use ($metadata, $key) { |
539
|
|
|
if ($key === Persister::POLYMORPHIC_KEY) { |
540
|
|
|
return $value; |
541
|
|
|
} |
542
|
|
|
$strategy = ($metadata->persistence instanceof StorageMetadata) ? $metadata->persistence->idStrategy : null; |
543
|
|
|
return $this->getIdentifierDbValue($value, $strategy); |
544
|
|
|
}; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Determines whether a query value has additional query operators. |
549
|
|
|
* |
550
|
|
|
* @param mixed $value |
551
|
|
|
* @return bool |
552
|
|
|
*/ |
553
|
|
|
private function hasOperators($value) |
554
|
|
|
{ |
555
|
|
|
|
556
|
|
|
if (!is_array($value) && !is_object($value)) { |
557
|
|
|
return false; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
if (is_object($value)) { |
561
|
|
|
$value = get_object_vars($value); |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
foreach ($value as $key => $subValue) { |
565
|
|
|
if (true === $this->isOperator($key)) { |
566
|
|
|
return true; |
567
|
|
|
} |
568
|
|
|
} |
569
|
|
|
return false; |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* Determines if a key is a query operator. |
574
|
|
|
* |
575
|
|
|
* @param string $key |
576
|
|
|
* @return bool |
577
|
|
|
*/ |
578
|
|
|
private function isOperator($key) |
579
|
|
|
{ |
580
|
|
|
return isset($key[0]) && '$' === $key[0]; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* Determines if a key is of a certain operator handling type. |
585
|
|
|
* |
586
|
|
|
* @param string $type |
587
|
|
|
* @param string $key |
588
|
|
|
* @return bool |
589
|
|
|
*/ |
590
|
|
|
private function isOpType($type, $key) |
591
|
|
|
{ |
592
|
|
|
if (!isset($this->ops[$type])) { |
593
|
|
|
return false; |
594
|
|
|
} |
595
|
|
|
return in_array($key, $this->ops[$type]); |
596
|
|
|
} |
597
|
|
|
} |
598
|
|
|
|
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.