1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace As3\Modlr\Persister\MongoDb; |
4
|
|
|
|
5
|
|
|
use As3\Modlr\Store\Store; |
6
|
|
|
use As3\Modlr\Models\Model; |
7
|
|
|
use As3\Modlr\Models\Collection; |
8
|
|
|
use As3\Modlr\Metadata\EntityMetadata; |
9
|
|
|
use As3\Modlr\Metadata\AttributeMetadata; |
10
|
|
|
use As3\Modlr\Metadata\RelationshipMetadata; |
11
|
|
|
use As3\Modlr\Persister\PersisterInterface; |
12
|
|
|
use As3\Modlr\Persister\PersisterException; |
13
|
|
|
use As3\Modlr\Persister\Record; |
14
|
|
|
use Doctrine\MongoDB\Connection; |
15
|
|
|
use \MongoId; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Persists and retrieves models to/from a MongoDB database. |
19
|
|
|
* |
20
|
|
|
* @author Jacob Bare <[email protected]> |
21
|
|
|
*/ |
22
|
|
|
final class Persister implements PersisterInterface |
23
|
|
|
{ |
24
|
|
|
const IDENTIFIER_KEY = '_id'; |
25
|
|
|
const POLYMORPHIC_KEY = '_type'; |
26
|
|
|
const PERSISTER_KEY = 'mongodb'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* The Doctine MongoDB connection. |
30
|
|
|
* |
31
|
|
|
* @var Connection |
32
|
|
|
*/ |
33
|
|
|
private $connection; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* The query/database operations formatter. |
37
|
|
|
* |
38
|
|
|
* @var Formatter |
39
|
|
|
*/ |
40
|
|
|
private $formatter; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var StorageMetadataFactory |
44
|
|
|
*/ |
45
|
|
|
private $smf; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Constructor. |
49
|
|
|
* |
50
|
|
|
* @param Connection $connection |
51
|
|
|
* @param StorageMetadataFactory $smf |
52
|
|
|
*/ |
53
|
|
|
public function __construct(Connection $connection, StorageMetadataFactory $smf) |
54
|
|
|
{ |
55
|
|
|
$this->connection = $connection; |
56
|
|
|
$this->formatter = new Formatter(); |
57
|
|
|
$this->smf = $smf; |
58
|
|
|
|
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* {@inheritDoc} |
63
|
|
|
*/ |
64
|
|
|
public function getPersisterKey() |
65
|
|
|
{ |
66
|
|
|
return self::PERSISTER_KEY; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* {@inheritDoc} |
71
|
|
|
*/ |
72
|
|
|
public function getPersistenceMetadataFactory() |
73
|
|
|
{ |
74
|
|
|
return $this->smf; |
|
|
|
|
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* {@inheritDoc} |
79
|
|
|
* @todo Implement sorting and pagination (limit/skip). |
80
|
|
|
*/ |
81
|
|
|
public function all(EntityMetadata $metadata, Store $store, array $identifiers = []) |
82
|
|
|
{ |
83
|
|
|
$criteria = $this->getRetrieveCritiera($metadata, $identifiers); |
84
|
|
|
$cursor = $this->doQuery($metadata, $store, $criteria); |
85
|
|
|
return $this->hydrateRecords($metadata, $cursor->toArray(), $store); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* {@inheritDoc} |
90
|
|
|
*/ |
91
|
|
|
public function query(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0) |
92
|
|
|
{ |
93
|
|
|
$cursor = $this->doQuery($metadata, $store, $criteria); |
94
|
|
|
return $this->hydrateRecords($metadata, $cursor->toArray(), $store); |
|
|
|
|
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* {@inheritDoc} |
99
|
|
|
*/ |
100
|
|
|
public function inverse(EntityMetadata $owner, EntityMetadata $rel, Store $store, array $identifiers, $inverseField) |
101
|
|
|
{ |
102
|
|
|
$criteria = $this->getInverseCriteria($owner, $rel, $identifiers, $inverseField); |
103
|
|
|
$cursor = $this->doQuery($rel, $store, $criteria); |
104
|
|
|
return $this->hydrateRecords($rel, $cursor->toArray(), $store); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* {@inheritDoc} |
109
|
|
|
*/ |
110
|
|
|
public function retrieve(EntityMetadata $metadata, $identifier, Store $store) |
111
|
|
|
{ |
112
|
|
|
$criteria = $this->getRetrieveCritiera($metadata, $identifier); |
113
|
|
|
$result = $this->doQuery($metadata, $store, $criteria)->getSingleResult(); |
114
|
|
|
if (null === $result) { |
115
|
|
|
return; |
116
|
|
|
} |
117
|
|
|
return $this->hydrateRecord($metadata, $result, $store); |
|
|
|
|
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* {@inheritDoc} |
122
|
|
|
* @todo Optimize the changeset to query generation. |
123
|
|
|
*/ |
124
|
|
|
public function create(Model $model) |
125
|
|
|
{ |
126
|
|
|
$metadata = $model->getMetadata(); |
127
|
|
|
$insert[$this->getIdentifierKey()] = $this->convertId($model->getId()); |
|
|
|
|
128
|
|
|
if (true === $metadata->isChildEntity()) { |
129
|
|
|
$insert[$this->getPolymorphicKey()] = $metadata->type; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
$changeset = $model->getChangeSet(); |
133
|
|
View Code Duplication |
foreach ($changeset['attributes'] as $key => $values) { |
|
|
|
|
134
|
|
|
$value = $this->prepareAttribute($metadata->getAttribute($key), $values['new']); |
|
|
|
|
135
|
|
|
if (null === $value) { |
136
|
|
|
continue; |
137
|
|
|
} |
138
|
|
|
$insert[$key] = $value; |
139
|
|
|
} |
140
|
|
View Code Duplication |
foreach ($changeset['hasOne'] as $key => $values) { |
|
|
|
|
141
|
|
|
$value = $this->prepareHasOne($metadata->getRelationship($key), $values['new']); |
|
|
|
|
142
|
|
|
if (null === $value) { |
143
|
|
|
continue; |
144
|
|
|
} |
145
|
|
|
$insert[$key] = $value; |
146
|
|
|
} |
147
|
|
View Code Duplication |
foreach ($changeset['hasMany'] as $key => $values) { |
|
|
|
|
148
|
|
|
$value = $this->prepareHasMany($metadata->getRelationship($key), $values['new']); |
|
|
|
|
149
|
|
|
if (null === $value) { |
150
|
|
|
continue; |
151
|
|
|
} |
152
|
|
|
$insert[$key] = $value; |
153
|
|
|
} |
154
|
|
|
$this->createQueryBuilder($metadata) |
155
|
|
|
->insert() |
156
|
|
|
->setNewObj($insert) |
157
|
|
|
->getQuery() |
158
|
|
|
->execute() |
159
|
|
|
; |
160
|
|
|
return $model; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Prepares and formats an attribute value for proper insertion into the database. |
165
|
|
|
* |
166
|
|
|
* @deprecated |
167
|
|
|
* @param AttributeMetadata $attrMeta |
168
|
|
|
* @param mixed $value |
169
|
|
|
* @return mixed |
170
|
|
|
*/ |
171
|
|
View Code Duplication |
protected function prepareAttribute(AttributeMetadata $attrMeta, $value) |
|
|
|
|
172
|
|
|
{ |
173
|
|
|
// Handle data type conversion, if needed. |
174
|
|
|
if ('date' === $attrMeta->dataType && $value instanceof \DateTime) { |
175
|
|
|
return new \MongoDate($value->getTimestamp(), $value->format('u')); |
176
|
|
|
} |
177
|
|
|
return $value; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Prepares and formats a has-one relationship model for proper insertion into the database. |
182
|
|
|
* |
183
|
|
|
* @deprecated |
184
|
|
|
* @param RelationshipMetadata $relMeta |
185
|
|
|
* @param Model|null $model |
186
|
|
|
* @return mixed |
187
|
|
|
*/ |
188
|
|
View Code Duplication |
protected function prepareHasOne(RelationshipMetadata $relMeta, Model $model = null) |
|
|
|
|
189
|
|
|
{ |
190
|
|
|
if (null === $model || true === $relMeta->isInverse) { |
191
|
|
|
return null; |
192
|
|
|
} |
193
|
|
|
return $this->createReference($relMeta, $model); |
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Prepares and formats a has-many relationship model set for proper insertion into the database. |
198
|
|
|
* |
199
|
|
|
* @deprecated |
200
|
|
|
* @param RelationshipMetadata $relMeta |
201
|
|
|
* @param Model[]|null $models |
202
|
|
|
* @return mixed |
203
|
|
|
*/ |
204
|
|
View Code Duplication |
protected function prepareHasMany(RelationshipMetadata $relMeta, array $models = null) |
|
|
|
|
205
|
|
|
{ |
206
|
|
|
if (null === $models || true === $relMeta->isInverse) { |
207
|
|
|
return null; |
208
|
|
|
} |
209
|
|
|
$references = []; |
210
|
|
|
foreach ($models as $model) { |
211
|
|
|
$references[] = $this->createReference($relMeta, $model); |
|
|
|
|
212
|
|
|
} |
213
|
|
|
return empty($references) ? null : $references; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Creates a reference for storage of a related model in the database |
218
|
|
|
* |
219
|
|
|
* @deprecated |
220
|
|
|
* @param RelationshipMetadata $relMeta |
221
|
|
|
* @param Model $model |
222
|
|
|
* @return mixed |
223
|
|
|
*/ |
224
|
|
|
protected function createReference(RelationshipMetadata $relMeta, Model $model) |
225
|
|
|
{ |
226
|
|
View Code Duplication |
if (true === $relMeta->isPolymorphic()) { |
|
|
|
|
227
|
|
|
$reference[$this->getIdentifierKey()] = $this->convertId($model->getId()); |
|
|
|
|
228
|
|
|
$reference[$this->getPolymorphicKey()] = $model->getType(); |
229
|
|
|
return $reference; |
230
|
|
|
} |
231
|
|
|
return $this->convertId($model->getId()); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* {@inheritDoc} |
236
|
|
|
* @todo Optimize the changeset to query generation. |
237
|
|
|
*/ |
238
|
|
|
public function update(Model $model) |
239
|
|
|
{ |
240
|
|
|
$metadata = $model->getMetadata(); |
241
|
|
|
$criteria = $this->getRetrieveCritiera($metadata, $model->getId()); |
242
|
|
|
$changeset = $model->getChangeSet(); |
243
|
|
|
|
244
|
|
|
$update = []; |
245
|
|
View Code Duplication |
foreach ($changeset['attributes'] as $key => $values) { |
|
|
|
|
246
|
|
|
if (null === $values['new']) { |
247
|
|
|
$op = '$unset'; |
248
|
|
|
$value = 1; |
249
|
|
|
} else { |
250
|
|
|
$op = '$set'; |
251
|
|
|
$value = $this->prepareAttribute($metadata->getAttribute($key), $values['new']); |
|
|
|
|
252
|
|
|
} |
253
|
|
|
$update[$op][$key] = $value; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// @todo Must prevent inverse relationships from persisting |
257
|
|
View Code Duplication |
foreach ($changeset['hasOne'] as $key => $values) { |
|
|
|
|
258
|
|
|
if (null === $values['new']) { |
259
|
|
|
$op = '$unset'; |
260
|
|
|
$value = 1; |
261
|
|
|
} else { |
262
|
|
|
$op = '$set'; |
263
|
|
|
$value = $this->prepareHasOne($metadata->getRelationship($key), $values['new']); |
|
|
|
|
264
|
|
|
} |
265
|
|
|
$update[$op][$key] = $value; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
View Code Duplication |
foreach ($changeset['hasMany'] as $key => $values) { |
|
|
|
|
269
|
|
|
if (null === $values['new']) { |
270
|
|
|
$op = '$unset'; |
271
|
|
|
$value = 1; |
272
|
|
|
} else { |
273
|
|
|
$op = '$set'; |
274
|
|
|
$value = $this->prepareHasMany($metadata->getRelationship($key), $values['new']); |
|
|
|
|
275
|
|
|
} |
276
|
|
|
$update[$op][$key] = $value; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
if (empty($update)) { |
280
|
|
|
return $model; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
$this->createQueryBuilder($metadata) |
284
|
|
|
->update() |
285
|
|
|
->setQueryArray($criteria) |
286
|
|
|
->setNewObj($update) |
287
|
|
|
->getQuery() |
288
|
|
|
->execute(); |
289
|
|
|
; |
290
|
|
|
return $model; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* {@inheritDoc} |
295
|
|
|
*/ |
296
|
|
|
public function delete(Model $model) |
297
|
|
|
{ |
298
|
|
|
$metadata = $model->getMetadata(); |
299
|
|
|
$criteria = $this->getRetrieveCritiera($metadata, $model->getId()); |
300
|
|
|
|
301
|
|
|
$this->createQueryBuilder($metadata) |
302
|
|
|
->remove() |
303
|
|
|
->setQueryArray($criteria) |
304
|
|
|
->getQuery() |
305
|
|
|
->execute(); |
306
|
|
|
; |
307
|
|
|
return $model; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* {@inheritDoc} |
312
|
|
|
*/ |
313
|
|
|
public function generateId($strategy = null) |
314
|
|
|
{ |
315
|
|
|
if (false === $this->getFormatter()->isIdStrategySupported($strategy)) { |
316
|
|
|
throw PersisterException::nyi('ID generation currently only supports an object strategy, or none at all.'); |
317
|
|
|
} |
318
|
|
|
return new MongoId(); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* @return Formatter |
323
|
|
|
*/ |
324
|
|
|
public function getFormatter() |
325
|
|
|
{ |
326
|
|
|
return $this->formatter; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* {@inheritDoc} |
331
|
|
|
*/ |
332
|
|
|
public function convertId($identifier, $strategy = null) |
333
|
|
|
{ |
334
|
|
|
return $this->getFormatter()->getIdentifierDbValue($identifier, $strategy); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* {@inheritDoc} |
339
|
|
|
*/ |
340
|
|
|
public function getIdentifierKey() |
341
|
|
|
{ |
342
|
|
|
return self::IDENTIFIER_KEY; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* {@inheritDoc} |
347
|
|
|
*/ |
348
|
|
|
public function getPolymorphicKey() |
349
|
|
|
{ |
350
|
|
|
return self::POLYMORPHIC_KEY; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* {@inheritDoc} |
355
|
|
|
*/ |
356
|
|
|
public function extractType(EntityMetadata $metadata, array $data) |
357
|
|
|
{ |
358
|
|
|
if (false === $metadata->isPolymorphic()) { |
359
|
|
|
return $metadata->type; |
360
|
|
|
} |
361
|
|
|
if (!isset($data[$this->getPolymorphicKey()])) { |
362
|
|
|
throw PersisterException::badRequest(sprintf('Unable to extract polymorphic type. The "%s" key was not found.', $this->getPolymorphicKey())); |
363
|
|
|
} |
364
|
|
|
return $data[$this->getPolymorphicKey()]; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Finds records from the database based on the provided metadata and criteria. |
369
|
|
|
* |
370
|
|
|
* @param EntityMetadata $metadata The model metadata that the database should query against. |
371
|
|
|
* @param Store $store The store. |
372
|
|
|
* @param array $criteria The query criteria. |
373
|
|
|
* @param array $fields Fields to include/exclude. |
374
|
|
|
* @param array $sort The sort criteria. |
375
|
|
|
* @param int $offset The starting offset, aka the number of Models to skip. |
376
|
|
|
* @param int $limit The number of Models to limit. |
377
|
|
|
* @return \Doctrine\MongoDB\Cursor |
378
|
|
|
*/ |
379
|
|
|
protected function doQuery(EntityMetadata $metadata, Store $store, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0) |
|
|
|
|
380
|
|
|
{ |
381
|
|
|
$criteria = $this->getFormatter()->formatQuery($metadata, $store, $criteria); |
382
|
|
|
return $this->createQueryBuilder($metadata) |
383
|
|
|
->find() |
384
|
|
|
->setQueryArray($criteria) |
385
|
|
|
->getQuery() |
386
|
|
|
->execute() |
387
|
|
|
; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Processes multiple, raw MongoDB results an converts them into an array of standardized Record objects. |
392
|
|
|
* |
393
|
|
|
* @param EntityMetadata $metadata |
394
|
|
|
* @param array $results |
395
|
|
|
* @param Store $store |
396
|
|
|
* @return Record[] |
397
|
|
|
*/ |
398
|
|
|
protected function hydrateRecords(EntityMetadata $metadata, array $results, Store $store) |
399
|
|
|
{ |
400
|
|
|
$records = []; |
401
|
|
|
foreach ($results as $data) { |
402
|
|
|
$records[] = $this->hydrateRecord($metadata, $data, $store); |
403
|
|
|
} |
404
|
|
|
return $records; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Processes raw MongoDB data an converts it into a standardized Record object. |
409
|
|
|
* |
410
|
|
|
* @param EntityMetadata $metadata |
411
|
|
|
* @param array $data |
412
|
|
|
* @param Store $store |
413
|
|
|
* @return Record |
414
|
|
|
*/ |
415
|
|
|
protected function hydrateRecord(EntityMetadata $metadata, array $data, Store $store) |
416
|
|
|
{ |
417
|
|
|
$identifier = $data[$this->getIdentifierKey()]; |
418
|
|
|
unset($data[$this->getIdentifierKey()]); |
419
|
|
|
|
420
|
|
|
$type = $this->extractType($metadata, $data); |
421
|
|
|
unset($data[$this->getPolymorphicKey()]); |
422
|
|
|
|
423
|
|
|
$metadata = $store->getMetadataForType($type); |
424
|
|
|
foreach ($metadata->getRelationships() as $key => $relMeta) { |
425
|
|
|
if (!isset($data[$key])) { |
426
|
|
|
continue; |
427
|
|
|
} |
428
|
|
|
if (true === $relMeta->isMany() && !is_array($data[$key])) { |
429
|
|
|
throw PersisterException::badRequest(sprintf('Relationship key "%s" is a reference many. Expected record data type of array, "%s" found on model "%s" for identifier "%s"', $key, gettype($data[$key]), $type, $identifier)); |
430
|
|
|
} |
431
|
|
|
$references = $relMeta->isOne() ? [$data[$key]] : $data[$key]; |
432
|
|
|
|
433
|
|
|
$extracted = []; |
434
|
|
|
foreach ($references as $reference) { |
435
|
|
|
$extracted[] = $this->extractRelationship($relMeta, $reference); |
436
|
|
|
} |
437
|
|
|
$data[$key] = $relMeta->isOne() ? reset($extracted) : $extracted; |
438
|
|
|
} |
439
|
|
|
return new Record($type, $identifier, $data); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Extracts a standard relationship array that the store expects from a raw MongoDB reference value. |
444
|
|
|
* |
445
|
|
|
* @param RelationshipMetadata $relMeta |
446
|
|
|
* @param mixed $reference |
447
|
|
|
* @return array |
448
|
|
|
* @throws \RuntimeException If the relationship could not be extracted. |
449
|
|
|
*/ |
450
|
|
|
protected function extractRelationship(RelationshipMetadata $relMeta, $reference) |
451
|
|
|
{ |
452
|
|
|
$simple = false === $relMeta->isPolymorphic(); |
453
|
|
|
$idKey = $this->getIdentifierKey(); |
454
|
|
|
$typeKey = $this->getPolymorphicKey(); |
455
|
|
|
if (true === $simple && is_array($reference) && isset($reference[$idKey])) { |
456
|
|
|
return [ |
457
|
|
|
'id' => $reference[$idKey], |
458
|
|
|
'type' => $relMeta->getEntityType(), |
459
|
|
|
]; |
460
|
|
|
} elseif (true === $simple && !is_array($reference)) { |
461
|
|
|
return [ |
462
|
|
|
'id' => $reference, |
463
|
|
|
'type' => $relMeta->getEntityType(), |
464
|
|
|
]; |
465
|
|
|
} elseif (false === $simple && is_array($reference) && isset($reference[$idKey]) && isset($reference[$typeKey])) { |
466
|
|
|
return [ |
467
|
|
|
'id' => $reference[$idKey], |
468
|
|
|
'type' => $reference[$typeKey], |
469
|
|
|
]; |
470
|
|
|
} else { |
471
|
|
|
throw new RuntimeException('Unable to extract a reference id.'); |
472
|
|
|
} |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Gets standard database retrieval criteria for an inverse relationship. |
477
|
|
|
* |
478
|
|
|
* @param EntityMetadata $metadata The entity to retrieve database records for. |
|
|
|
|
479
|
|
|
* @param string|array $identifiers The IDs to query. |
480
|
|
|
* @return array |
481
|
|
|
*/ |
482
|
|
|
protected function getInverseCriteria(EntityMetadata $owner, EntityMetadata $related, $identifiers, $inverseField) |
483
|
|
|
{ |
484
|
|
|
$criteria[$inverseField] = $this->getIdentifierCriteria($identifiers); |
|
|
|
|
485
|
|
|
if (true === $owner->isChildEntity()) { |
486
|
|
|
// The owner is owned by a polymorphic model. Must include the type with the inverse field criteria. |
487
|
|
|
$criteria[$inverseField] = [ |
488
|
|
|
$this->getIdentifierKey() => $criteria[$inverseField], |
489
|
|
|
$this->getPolymorphicKey() => $owner->type, |
490
|
|
|
]; |
491
|
|
|
} |
492
|
|
|
if (true === $related->isChildEntity()) { |
493
|
|
|
// The relationship is owned by a polymorphic model. Must include the type in the root criteria. |
494
|
|
|
$criteria[$this->getPolymorphicKey()] = $related->type; |
495
|
|
|
} |
496
|
|
|
return $criteria; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Gets standard database retrieval criteria for an entity and the provided identifiers. |
501
|
|
|
* |
502
|
|
|
* @param EntityMetadata $metadata The entity to retrieve database records for. |
503
|
|
|
* @param string|array|null $identifiers The IDs to query. |
504
|
|
|
* @return array |
505
|
|
|
*/ |
506
|
|
|
protected function getRetrieveCritiera(EntityMetadata $metadata, $identifiers = null) |
507
|
|
|
{ |
508
|
|
|
$criteria = []; |
509
|
|
|
if (true === $metadata->isChildEntity()) { |
510
|
|
|
$criteria[$this->getPolymorphicKey()] = $metadata->type; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
if (null === $identifiers) { |
514
|
|
|
return $criteria; |
515
|
|
|
} |
516
|
|
|
$identifiers = (array) $identifiers; |
517
|
|
|
if (empty($identifiers)) { |
518
|
|
|
return $criteria; |
519
|
|
|
} |
520
|
|
|
$criteria[$this->getIdentifierKey()] = (1 === count($identifiers)) ? $identifiers[0] : $identifiers; |
521
|
|
|
return $criteria; |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
/** |
525
|
|
|
* Creates a builder object for querying MongoDB based on the provided metadata. |
526
|
|
|
* |
527
|
|
|
* @param EntityMetadata $metadata |
528
|
|
|
* @return \Doctrine\MongoDB\Query\Builder |
529
|
|
|
*/ |
530
|
|
|
protected function createQueryBuilder(EntityMetadata $metadata) |
531
|
|
|
{ |
532
|
|
|
return $this->getModelCollection($metadata)->createQueryBuilder(); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
/** |
536
|
|
|
* Gets the MongoDB Collection object for a Model. |
537
|
|
|
* |
538
|
|
|
* @param EntityMetadata $metadata |
539
|
|
|
* @return \Doctrine\MongoDB\Collection |
540
|
|
|
*/ |
541
|
|
|
protected function getModelCollection(EntityMetadata $metadata) |
542
|
|
|
{ |
543
|
|
|
return $this->connection->selectCollection($metadata->persistence->db, $metadata->persistence->collection); |
|
|
|
|
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
/** |
547
|
|
|
* Determines if the current id strategy is supported. |
548
|
|
|
* |
549
|
|
|
* @deprecated |
550
|
|
|
* @param string|null $strategy |
551
|
|
|
* @return bool |
552
|
|
|
*/ |
553
|
|
|
protected function isIdStrategySupported($strategy) |
554
|
|
|
{ |
555
|
|
|
return (null === $strategy || 'object' === $strategy); |
556
|
|
|
} |
557
|
|
|
} |
558
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.