Completed
Pull Request — master (#2025)
by Andreas
15:33
created

DocumentPersister::handleCollections()   B

Complexity

Conditions 8
Paths 72

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 18
cts 18
cp 1
rs 8.1795
c 0
b 0
f 0
cc 8
nc 72
nop 2
crap 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Persisters;
6
7
use BadMethodCallException;
8
use DateTime;
9
use Doctrine\Common\Persistence\Mapping\MappingException;
10
use Doctrine\ODM\MongoDB\DocumentManager;
11
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
12
use Doctrine\ODM\MongoDB\Iterator\CachingIterator;
13
use Doctrine\ODM\MongoDB\Iterator\HydratingIterator;
14
use Doctrine\ODM\MongoDB\Iterator\Iterator;
15
use Doctrine\ODM\MongoDB\Iterator\PrimingIterator;
16
use Doctrine\ODM\MongoDB\LockException;
17
use Doctrine\ODM\MongoDB\LockMode;
18
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
19
use Doctrine\ODM\MongoDB\MongoDBException;
20
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionException;
21
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
22
use Doctrine\ODM\MongoDB\Query\CriteriaMerger;
23
use Doctrine\ODM\MongoDB\Query\Query;
24
use Doctrine\ODM\MongoDB\Query\ReferencePrimer;
25
use Doctrine\ODM\MongoDB\Types\Type;
26
use Doctrine\ODM\MongoDB\UnitOfWork;
27
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
28
use InvalidArgumentException;
29
use MongoDB\BSON\ObjectId;
30
use MongoDB\Collection;
31
use MongoDB\Driver\Cursor;
32
use MongoDB\Driver\Exception\Exception as DriverException;
33
use MongoDB\Driver\Exception\WriteException;
34
use MongoDB\GridFS\Bucket;
35
use ProxyManager\Proxy\GhostObjectInterface;
36
use stdClass;
37
use function array_combine;
38
use function array_fill;
39
use function array_intersect_key;
40
use function array_keys;
41
use function array_map;
42
use function array_merge;
43
use function array_search;
44
use function array_slice;
45
use function array_values;
46
use function assert;
47
use function count;
48
use function explode;
49
use function get_class;
50
use function get_object_vars;
51
use function implode;
52
use function in_array;
53
use function is_array;
54
use function is_object;
55
use function is_scalar;
56
use function is_string;
57
use function max;
58
use function spl_object_hash;
59
use function sprintf;
60
use function strpos;
61
use function strtolower;
62
63
/**
64
 * The DocumentPersister is responsible for persisting documents.
65
 *
66
 * @internal
67
 */
68
final class DocumentPersister
69
{
70
    /** @var PersistenceBuilder */
71
    private $pb;
72
73
    /** @var DocumentManager */
74
    private $dm;
75
76
    /** @var UnitOfWork */
77
    private $uow;
78
79
    /** @var ClassMetadata */
80
    private $class;
81
82
    /** @var Collection|null */
83
    private $collection;
84
85
    /** @var Bucket|null */
86
    private $bucket;
87
88
    /**
89
     * Array of queued inserts for the persister to insert.
90
     *
91
     * @var array
92
     */
93
    private $queuedInserts = [];
94
95
    /**
96
     * Array of queued inserts for the persister to insert.
97
     *
98
     * @var array
99
     */
100
    private $queuedUpserts = [];
101
102
    /** @var CriteriaMerger */
103
    private $cm;
104
105
    /** @var CollectionPersister */
106
    private $cp;
107
108
    /** @var HydratorFactory */
109
    private $hydratorFactory;
110
111 1203
    public function __construct(
112
        PersistenceBuilder $pb,
113
        DocumentManager $dm,
114
        UnitOfWork $uow,
115
        HydratorFactory $hydratorFactory,
116
        ClassMetadata $class,
117
        ?CriteriaMerger $cm = null
118
    ) {
119 1203
        $this->pb              = $pb;
120 1203
        $this->dm              = $dm;
121 1203
        $this->cm              = $cm ?: new CriteriaMerger();
122 1203
        $this->uow             = $uow;
123 1203
        $this->hydratorFactory = $hydratorFactory;
124 1203
        $this->class           = $class;
125 1203
        $this->cp              = $this->uow->getCollectionPersister();
126
127 1203
        if ($class->isEmbeddedDocument || $class->isQueryResultDocument) {
128 95
            return;
129
        }
130
131 1200
        $this->collection = $dm->getDocumentCollection($class->name);
132
133 1200
        if (! $class->isFile) {
134 1188
            return;
135
        }
136
137 20
        $this->bucket = $dm->getDocumentBucket($class->name);
138 20
    }
139
140
    public function getInserts() : array
141
    {
142
        return $this->queuedInserts;
143
    }
144
145
    public function isQueuedForInsert(object $document) : bool
146
    {
147
        return isset($this->queuedInserts[spl_object_hash($document)]);
148
    }
149
150
    /**
151
     * Adds a document to the queued insertions.
152
     * The document remains queued until {@link executeInserts} is invoked.
153
     */
154 533
    public function addInsert(object $document) : void
155
    {
156 533
        $this->queuedInserts[spl_object_hash($document)] = $document;
157 533
    }
158
159
    public function getUpserts() : array
160
    {
161
        return $this->queuedUpserts;
162
    }
163
164
    public function isQueuedForUpsert(object $document) : bool
165
    {
166
        return isset($this->queuedUpserts[spl_object_hash($document)]);
167
    }
168
169
    /**
170
     * Adds a document to the queued upserts.
171
     * The document remains queued until {@link executeUpserts} is invoked.
172
     */
173 85
    public function addUpsert(object $document) : void
174
    {
175 85
        $this->queuedUpserts[spl_object_hash($document)] = $document;
176 85
    }
177
178
    /**
179
     * Gets the ClassMetadata instance of the document class this persister is
180
     * used for.
181
     */
182
    public function getClassMetadata() : ClassMetadata
183
    {
184
        return $this->class;
185
    }
186
187
    /**
188
     * Executes all queued document insertions.
189
     *
190
     * Queued documents without an ID will inserted in a batch and queued
191
     * documents with an ID will be upserted individually.
192
     *
193
     * If no inserts are queued, invoking this method is a NOOP.
194
     *
195
     * @throws DriverException
196
     */
197 533
    public function executeInserts(array $options = []) : void
198
    {
199 533
        if (! $this->queuedInserts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->queuedInserts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
200
            return;
201
        }
202
203 533
        $inserts = [];
204 533
        $options = $this->getWriteOptions($options);
205 533
        foreach ($this->queuedInserts as $oid => $document) {
206 533
            $data = $this->pb->prepareInsertData($document);
207
208
            // Set the initial version for each insert
209 522
            if ($this->class->isVersioned) {
210 40
                $versionMapping = $this->class->fieldMappings[$this->class->versionField];
211 40
                $nextVersion    = null;
212 40
                if ($versionMapping['type'] === 'int') {
213 38
                    $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
214 38
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
215 2
                } elseif ($versionMapping['type'] === 'date') {
216 2
                    $nextVersionDateTime = new DateTime();
217 2
                    $nextVersion         = Type::convertPHPToDatabaseValue($nextVersionDateTime);
218 2
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
219
                }
220 40
                $data[$versionMapping['name']] = $nextVersion;
221
            }
222
223 522
            $inserts[] = $data;
224
        }
225
226 522
        if ($inserts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $inserts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
227
            try {
228 522
                assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
229 522
                $this->collection->insertMany($inserts, $options);
230 6
            } catch (DriverException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
231 6
                $this->queuedInserts = [];
232 6
                throw $e;
233
            }
234
        }
235
236
        /* All collections except for ones using addToSet have already been
237
         * saved. We have left these to be handled separately to avoid checking
238
         * collection for uniqueness on PHP side.
239
         */
240 522
        foreach ($this->queuedInserts as $document) {
241 522
            $this->handleCollections($document, $options);
242
        }
243
244 522
        $this->queuedInserts = [];
245 522
    }
246
247
    /**
248
     * Executes all queued document upserts.
249
     *
250
     * Queued documents with an ID are upserted individually.
251
     *
252
     * If no upserts are queued, invoking this method is a NOOP.
253
     */
254 85
    public function executeUpserts(array $options = []) : void
255
    {
256 85
        if (! $this->queuedUpserts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->queuedUpserts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
257
            return;
258
        }
259
260 85
        $options = $this->getWriteOptions($options);
261 85
        foreach ($this->queuedUpserts as $oid => $document) {
262
            try {
263 85
                $this->executeUpsert($document, $options);
264 85
                $this->handleCollections($document, $options);
265 85
                unset($this->queuedUpserts[$oid]);
266
            } catch (WriteException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\WriteException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
267
                unset($this->queuedUpserts[$oid]);
268
                throw $e;
269
            }
270
        }
271 85
    }
272
273
    /**
274
     * Executes a single upsert in {@link executeUpserts}
275
     */
276 85
    private function executeUpsert(object $document, array $options) : void
277
    {
278 85
        $options['upsert'] = true;
279 85
        $criteria          = $this->getQueryForDocument($document);
280
281 85
        $data = $this->pb->prepareUpsertData($document);
282
283
        // Set the initial version for each upsert
284 85
        if ($this->class->isVersioned) {
285 3
            $versionMapping = $this->class->fieldMappings[$this->class->versionField];
286 3
            $nextVersion    = null;
287 3
            if ($versionMapping['type'] === 'int') {
288 2
                $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
289 2
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
290 1
            } elseif ($versionMapping['type'] === 'date') {
291 1
                $nextVersionDateTime = new DateTime();
292 1
                $nextVersion         = Type::convertPHPToDatabaseValue($nextVersionDateTime);
293 1
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
294
            }
295 3
            $data['$set'][$versionMapping['name']] = $nextVersion;
296
        }
297
298 85
        foreach (array_keys($criteria) as $field) {
299 85
            unset($data['$set'][$field]);
300 85
            unset($data['$inc'][$field]);
301 85
            unset($data['$setOnInsert'][$field]);
302
        }
303
304
        // Do not send empty update operators
305 85
        foreach (['$set', '$inc', '$setOnInsert'] as $operator) {
306 85
            if (! empty($data[$operator])) {
307 70
                continue;
308
            }
309
310 85
            unset($data[$operator]);
311
        }
312
313
        /* If there are no modifiers remaining, we're upserting a document with
314
         * an identifier as its only field. Since a document with the identifier
315
         * may already exist, the desired behavior is "insert if not exists" and
316
         * NOOP otherwise. MongoDB 2.6+ does not allow empty modifiers, so $set
317
         * the identifier to the same value in our criteria.
318
         *
319
         * This will fail for versions before MongoDB 2.6, which require an
320
         * empty $set modifier. The best we can do (without attempting to check
321
         * server versions in advance) is attempt the 2.6+ behavior and retry
322
         * after the relevant exception.
323
         *
324
         * See: https://jira.mongodb.org/browse/SERVER-12266
325
         */
326 85
        if (empty($data)) {
327 16
            $retry = true;
328 16
            $data  = ['$set' => ['_id' => $criteria['_id']]];
329
        }
330
331
        try {
332 85
            assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
333 85
            $this->collection->updateOne($criteria, $data, $options);
334 85
            return;
335
        } catch (WriteException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\WriteException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
336
            if (empty($retry) || strpos($e->getMessage(), 'Mod on _id not allowed') === false) {
337
                throw $e;
338
            }
339
        }
340
341
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
342
        $this->collection->updateOne($criteria, ['$set' => new stdClass()], $options);
343
    }
344
345
    /**
346
     * Updates the already persisted document if it has any new changesets.
347
     *
348
     * @throws LockException
349
     */
350 234
    public function update(object $document, array $options = []) : void
351
    {
352 234
        $update = $this->pb->prepareUpdateData($document);
353
354 234
        $query = $this->getQueryForDocument($document);
355
356 232
        foreach (array_keys($query) as $field) {
357 232
            unset($update['$set'][$field]);
358
        }
359
360 232
        if (empty($update['$set'])) {
361 101
            unset($update['$set']);
362
        }
363
364
        // Include versioning logic to set the new version value in the database
365
        // and to ensure the version has not changed since this document object instance
366
        // was fetched from the database
367 232
        $nextVersion = null;
368 232
        if ($this->class->isVersioned) {
369 33
            $versionMapping = $this->class->fieldMappings[$this->class->versionField];
370 33
            $currentVersion = $this->class->reflFields[$this->class->versionField]->getValue($document);
371 33
            if ($versionMapping['type'] === 'int') {
372 30
                $nextVersion                             = $currentVersion + 1;
373 30
                $update['$inc'][$versionMapping['name']] = 1;
374 30
                $query[$versionMapping['name']]          = $currentVersion;
375 3
            } elseif ($versionMapping['type'] === 'date') {
376 3
                $nextVersion                             = new DateTime();
377 3
                $update['$set'][$versionMapping['name']] = Type::convertPHPToDatabaseValue($nextVersion);
378 3
                $query[$versionMapping['name']]          = Type::convertPHPToDatabaseValue($currentVersion);
379
            }
380
        }
381
382 232
        if (! empty($update)) {
383
            // Include locking logic so that if the document object in memory is currently
384
            // locked then it will remove it, otherwise it ensures the document is not locked.
385 158
            if ($this->class->isLockable) {
386 11
                $isLocked    = $this->class->reflFields[$this->class->lockField]->getValue($document);
387 11
                $lockMapping = $this->class->fieldMappings[$this->class->lockField];
388 11
                if ($isLocked) {
389 2
                    $update['$unset'] = [$lockMapping['name'] => true];
390
                } else {
391 9
                    $query[$lockMapping['name']] = ['$exists' => false];
392
                }
393
            }
394
395 158
            $options = $this->getWriteOptions($options);
396
397 158
            assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
398 158
            $result = $this->collection->updateOne($query, $update, $options);
399
400 158
            if (($this->class->isVersioned || $this->class->isLockable) && $result->getModifiedCount() !== 1) {
401 6
                throw LockException::lockFailed($document);
402 153
            } elseif ($this->class->isVersioned) {
403 28
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
404
            }
405
        }
406
407 227
        $this->handleCollections($document, $options);
408 227
    }
409
410
    /**
411
     * Removes document from mongo
412
     *
413
     * @throws LockException
414
     */
415 36
    public function delete(object $document, array $options = []) : void
416
    {
417 36
        if ($this->bucket instanceof Bucket) {
0 ignored issues
show
Bug introduced by
The class MongoDB\GridFS\Bucket does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
418 1
            $documentIdentifier = $this->uow->getDocumentIdentifier($document);
419 1
            $databaseIdentifier = $this->class->getDatabaseIdentifierValue($documentIdentifier);
420
421 1
            $this->bucket->delete($databaseIdentifier);
422
423 1
            return;
424
        }
425
426 35
        $query = $this->getQueryForDocument($document);
427
428 35
        if ($this->class->isLockable) {
429 2
            $query[$this->class->lockField] = ['$exists' => false];
430
        }
431
432 35
        $options = $this->getWriteOptions($options);
433
434 35
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
435 35
        $result = $this->collection->deleteOne($query, $options);
436
437 35
        if (($this->class->isVersioned || $this->class->isLockable) && ! $result->getDeletedCount()) {
438 2
            throw LockException::lockFailed($document);
439
        }
440 33
    }
441
442
    /**
443
     * Refreshes a managed document.
444
     */
445 23
    public function refresh(object $document) : void
446
    {
447 23
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
448 23
        $query = $this->getQueryForDocument($document);
449 23
        $data  = $this->collection->findOne($query);
450 23
        if ($data === null) {
451
            throw MongoDBException::cannotRefreshDocument();
452
        }
453 23
        $data = $this->hydratorFactory->hydrate($document, (array) $data);
454 23
        $this->uow->setOriginalDocumentData($document, $data);
455 23
    }
456
457
    /**
458
     * Finds a document by a set of criteria.
459
     *
460
     * If a scalar or MongoDB\BSON\ObjectId is provided for $criteria, it will
461
     * be used to match an _id value.
462
     *
463
     * @param mixed $criteria Query criteria
464
     *
465
     * @throws LockException
466
     *
467
     * @todo Check identity map? loadById method? Try to guess whether
468
     *     $criteria is the id?
469
     */
470 366
    public function load($criteria, ?object $document = null, array $hints = [], int $lockMode = 0, ?array $sort = null) : ?object
0 ignored issues
show
Unused Code introduced by
The parameter $lockMode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
471
    {
472
        // TODO: remove this
473 366
        if ($criteria === null || is_scalar($criteria) || $criteria instanceof ObjectId) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\ObjectId does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
474
            $criteria = ['_id' => $criteria];
475
        }
476
477 366
        $criteria = $this->prepareQueryOrNewObj($criteria);
0 ignored issues
show
Bug introduced by
It seems like $criteria can also be of type object; however, Doctrine\ODM\MongoDB\Per...:prepareQueryOrNewObj() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
478 366
        $criteria = $this->addDiscriminatorToPreparedQuery($criteria);
479 366
        $criteria = $this->addFilterToPreparedQuery($criteria);
480
481 366
        $options = [];
482 366
        if ($sort !== null) {
483 95
            $options['sort'] = $this->prepareSort($sort);
484
        }
485 366
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
486 366
        $result = $this->collection->findOne($criteria, $options);
487 366
        $result = $result !== null ? (array) $result : null;
488
489 366
        if ($this->class->isLockable) {
490 1
            $lockMapping = $this->class->fieldMappings[$this->class->lockField];
491 1
            if (isset($result[$lockMapping['name']]) && $result[$lockMapping['name']] === LockMode::PESSIMISTIC_WRITE) {
492 1
                throw LockException::lockFailed($document);
493
            }
494
        }
495
496 365
        if ($result === null) {
497 115
            return null;
498
        }
499
500 321
        return $this->createDocument($result, $document, $hints);
501
    }
502
503
    /**
504
     * Finds documents by a set of criteria.
505
     */
506 24
    public function loadAll(array $criteria = [], ?array $sort = null, ?int $limit = null, ?int $skip = null) : Iterator
507
    {
508 24
        $criteria = $this->prepareQueryOrNewObj($criteria);
509 24
        $criteria = $this->addDiscriminatorToPreparedQuery($criteria);
510 24
        $criteria = $this->addFilterToPreparedQuery($criteria);
511
512 24
        $options = [];
513 24
        if ($sort !== null) {
514 11
            $options['sort'] = $this->prepareSort($sort);
515
        }
516
517 24
        if ($limit !== null) {
518 10
            $options['limit'] = $limit;
519
        }
520
521 24
        if ($skip !== null) {
522 1
            $options['skip'] = $skip;
523
        }
524
525 24
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
526 24
        $baseCursor = $this->collection->find($criteria, $options);
527 24
        return $this->wrapCursor($baseCursor);
528
    }
529
530
    /**
531
     * @throws MongoDBException
532
     */
533 314
    private function getShardKeyQuery(object $document) : array
534
    {
535 314
        if (! $this->class->isSharded()) {
536 304
            return [];
537
        }
538
539 10
        $shardKey = $this->class->getShardKey();
540 10
        $keys     = array_keys($shardKey['keys']);
541 10
        $data     = $this->uow->getDocumentActualData($document);
542
543 10
        $shardKeyQueryPart = [];
544 10
        foreach ($keys as $key) {
545 10
            assert(is_string($key));
546 10
            $mapping = $this->class->getFieldMappingByDbFieldName($key);
547 10
            $this->guardMissingShardKey($document, $key, $data);
548
549 8
            if (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
550 1
                $reference = $this->prepareReference(
551 1
                    $key,
552 1
                    $data[$mapping['fieldName']],
553 1
                    $mapping,
554 1
                    false
555
                );
556 1
                foreach ($reference as $keyValue) {
557 1
                    $shardKeyQueryPart[$keyValue[0]] = $keyValue[1];
558
                }
559
            } else {
560 7
                $value                   = Type::getType($mapping['type'])->convertToDatabaseValue($data[$mapping['fieldName']]);
561 7
                $shardKeyQueryPart[$key] = $value;
562
            }
563
        }
564
565 8
        return $shardKeyQueryPart;
566
    }
567
568
    /**
569
     * Wraps the supplied base cursor in the corresponding ODM class.
570
     */
571 24
    private function wrapCursor(Cursor $baseCursor) : Iterator
572
    {
573 24
        return new CachingIterator(new HydratingIterator($baseCursor, $this->dm->getUnitOfWork(), $this->class));
574
    }
575
576
    /**
577
     * Checks whether the given managed document exists in the database.
578
     */
579 3
    public function exists(object $document) : bool
580
    {
581 3
        $id = $this->class->getIdentifierObject($document);
582 3
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
583 3
        return (bool) $this->collection->findOne(['_id' => $id], ['_id']);
584
    }
585
586
    /**
587
     * Locks document by storing the lock mode on the mapped lock field.
588
     */
589 5
    public function lock(object $document, int $lockMode) : void
590
    {
591 5
        $id          = $this->uow->getDocumentIdentifier($document);
592 5
        $criteria    = ['_id' => $this->class->getDatabaseIdentifierValue($id)];
593 5
        $lockMapping = $this->class->fieldMappings[$this->class->lockField];
594 5
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
595 5
        $this->collection->updateOne($criteria, ['$set' => [$lockMapping['name'] => $lockMode]]);
596 5
        $this->class->reflFields[$this->class->lockField]->setValue($document, $lockMode);
597 5
    }
598
599
    /**
600
     * Releases any lock that exists on this document.
601
     */
602 1
    public function unlock(object $document) : void
603
    {
604 1
        $id          = $this->uow->getDocumentIdentifier($document);
605 1
        $criteria    = ['_id' => $this->class->getDatabaseIdentifierValue($id)];
606 1
        $lockMapping = $this->class->fieldMappings[$this->class->lockField];
607 1
        assert($this->collection instanceof Collection);
0 ignored issues
show
Bug introduced by
The class MongoDB\Collection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
608 1
        $this->collection->updateOne($criteria, ['$unset' => [$lockMapping['name'] => true]]);
609 1
        $this->class->reflFields[$this->class->lockField]->setValue($document, null);
610 1
    }
611
612
    /**
613
     * Creates or fills a single document object from an query result.
614
     *
615
     * @param array  $result   The query result.
616
     * @param object $document The document object to fill, if any.
617
     * @param array  $hints    Hints for document creation.
618
     *
619
     * @return object|null The filled and managed document object or NULL, if the query result is empty.
620
     */
621 321
    private function createDocument(array $result, ?object $document = null, array $hints = []) : ?object
622
    {
623 321
        if ($document !== null) {
624 26
            $hints[Query::HINT_REFRESH] = true;
625 26
            $id                         = $this->class->getPHPIdentifierValue($result['_id']);
626 26
            $this->uow->registerManaged($document, $id, $result);
627
        }
628
629 321
        return $this->uow->getOrCreateDocument($this->class->name, $result, $hints, $document);
630
    }
631
632
    /**
633
     * Loads a PersistentCollection data. Used in the initialize() method.
634
     */
635 178
    public function loadCollection(PersistentCollectionInterface $collection) : void
636
    {
637 178
        $mapping = $collection->getMapping();
638 178
        switch ($mapping['association']) {
639
            case ClassMetadata::EMBED_MANY:
640 126
                $this->loadEmbedManyCollection($collection);
641 126
                break;
642
643
            case ClassMetadata::REFERENCE_MANY:
644 75
                if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) {
645 5
                    $this->loadReferenceManyWithRepositoryMethod($collection);
646
                } else {
647 71
                    if ($mapping['isOwningSide']) {
648 59
                        $this->loadReferenceManyCollectionOwningSide($collection);
649
                    } else {
650 17
                        $this->loadReferenceManyCollectionInverseSide($collection);
651
                    }
652
                }
653 75
                break;
654
        }
655 178
    }
656
657 126
    private function loadEmbedManyCollection(PersistentCollectionInterface $collection) : void
658
    {
659 126
        $embeddedDocuments = $collection->getMongoData();
660 126
        $mapping           = $collection->getMapping();
661 126
        $owner             = $collection->getOwner();
662 126
        if (! $embeddedDocuments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $embeddedDocuments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
663 75
            return;
664
        }
665
666 97
        foreach ($embeddedDocuments as $key => $embeddedDocument) {
667 97
            $className              = $this->uow->getClassNameForAssociation($mapping, $embeddedDocument);
668 97
            $embeddedMetadata       = $this->dm->getClassMetadata($className);
669 97
            $embeddedDocumentObject = $embeddedMetadata->newInstance();
670
671 97
            $this->uow->setParentAssociation($embeddedDocumentObject, $mapping, $owner, $mapping['name'] . '.' . $key);
672
673 97
            $data = $this->hydratorFactory->hydrate($embeddedDocumentObject, $embeddedDocument, $collection->getHints());
674 97
            $id   = $data[$embeddedMetadata->identifier] ?? null;
675
676 97
            if (empty($collection->getHints()[Query::HINT_READ_ONLY])) {
677 96
                $this->uow->registerManaged($embeddedDocumentObject, $id, $data);
678
            }
679 97
            if (CollectionHelper::isHash($mapping['strategy'])) {
680 25
                $collection->set($key, $embeddedDocumentObject);
681
            } else {
682 80
                $collection->add($embeddedDocumentObject);
683
            }
684
        }
685 97
    }
686
687 59
    private function loadReferenceManyCollectionOwningSide(PersistentCollectionInterface $collection) : void
688
    {
689 59
        $hints      = $collection->getHints();
690 59
        $mapping    = $collection->getMapping();
691 59
        $groupedIds = [];
692
693 59
        $sorted = isset($mapping['sort']) && $mapping['sort'];
694
695 59
        foreach ($collection->getMongoData() as $key => $reference) {
696 53
            $className  = $this->uow->getClassNameForAssociation($mapping, $reference);
697 53
            $identifier = ClassMetadata::getReferenceId($reference, $mapping['storeAs']);
698 53
            $id         = $this->dm->getClassMetadata($className)->getPHPIdentifierValue($identifier);
699
700
            // create a reference to the class and id
701 53
            $reference = $this->dm->getReference($className, $id);
702
703
            // no custom sort so add the references right now in the order they are embedded
704 53
            if (! $sorted) {
705 52
                if (CollectionHelper::isHash($mapping['strategy'])) {
706 2
                    $collection->set($key, $reference);
707
                } else {
708 50
                    $collection->add($reference);
709
                }
710
            }
711
712
            // only query for the referenced object if it is not already initialized or the collection is sorted
713 53
            if (! (($reference instanceof GhostObjectInterface && ! $reference->isProxyInitialized())) && ! $sorted) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
714 22
                continue;
715
            }
716
717 38
            $groupedIds[$className][] = $identifier;
718
        }
719 59
        foreach ($groupedIds as $className => $ids) {
720 38
            $class           = $this->dm->getClassMetadata($className);
721 38
            $mongoCollection = $this->dm->getDocumentCollection($className);
722 38
            $criteria        = $this->cm->merge(
723 38
                ['_id' => ['$in' => array_values($ids)]],
724 38
                $this->dm->getFilterCollection()->getFilterCriteria($class),
725 38
                $mapping['criteria'] ?? []
726
            );
727 38
            $criteria        = $this->uow->getDocumentPersister($className)->prepareQueryOrNewObj($criteria);
728
729 38
            $options = [];
730 38
            if (isset($mapping['sort'])) {
731 38
                $options['sort'] = $this->prepareSort($mapping['sort']);
732
            }
733 38
            if (isset($mapping['limit'])) {
734
                $options['limit'] = $mapping['limit'];
735
            }
736 38
            if (isset($mapping['skip'])) {
737
                $options['skip'] = $mapping['skip'];
738
            }
739 38
            if (! empty($hints[Query::HINT_READ_PREFERENCE])) {
740
                $options['readPreference'] = $hints[Query::HINT_READ_PREFERENCE];
741
            }
742
743 38
            $cursor    = $mongoCollection->find($criteria, $options);
744 38
            $documents = $cursor->toArray();
745 38
            foreach ($documents as $documentData) {
746 37
                $document = $this->uow->getById($documentData['_id'], $class);
747 37
                if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
748 37
                    $data = $this->hydratorFactory->hydrate($document, $documentData);
749 37
                    $this->uow->setOriginalDocumentData($document, $data);
750
                }
751
752 37
                if (! $sorted) {
753 36
                    continue;
754
                }
755
756 1
                $collection->add($document);
757
            }
758
        }
759 59
    }
760
761 17
    private function loadReferenceManyCollectionInverseSide(PersistentCollectionInterface $collection) : void
762
    {
763 17
        $query    = $this->createReferenceManyInverseSideQuery($collection);
764 17
        $iterator = $query->execute();
765 17
        assert($iterator instanceof Iterator);
766 17
        $documents = $iterator->toArray();
767 17
        foreach ($documents as $key => $document) {
768 16
            $collection->add($document);
769
        }
770 17
    }
771
772 17
    public function createReferenceManyInverseSideQuery(PersistentCollectionInterface $collection) : Query
773
    {
774 17
        $hints   = $collection->getHints();
775 17
        $mapping = $collection->getMapping();
776 17
        $owner   = $collection->getOwner();
777
778 17
        if ($owner === null) {
779
            throw PersistentCollectionException::ownerRequiredToLoadCollection();
780
        }
781
782 17
        $ownerClass        = $this->dm->getClassMetadata(get_class($owner));
783 17
        $targetClass       = $this->dm->getClassMetadata($mapping['targetDocument']);
784 17
        $mappedByMapping   = $targetClass->fieldMappings[$mapping['mappedBy']] ?? [];
785 17
        $mappedByFieldName = ClassMetadata::getReferenceFieldName($mappedByMapping['storeAs'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF, $mapping['mappedBy']);
786
787 17
        $criteria = $this->cm->merge(
788 17
            [$mappedByFieldName => $ownerClass->getIdentifierObject($owner)],
789 17
            $this->dm->getFilterCollection()->getFilterCriteria($targetClass),
790 17
            $mapping['criteria'] ?? []
791
        );
792 17
        $criteria = $this->uow->getDocumentPersister($mapping['targetDocument'])->prepareQueryOrNewObj($criteria);
793 17
        $qb       = $this->dm->createQueryBuilder($mapping['targetDocument'])
794 17
            ->setQueryArray($criteria);
795
796 17
        if (isset($mapping['sort'])) {
797 17
            $qb->sort($mapping['sort']);
798
        }
799 17
        if (isset($mapping['limit'])) {
800 2
            $qb->limit($mapping['limit']);
801
        }
802 17
        if (isset($mapping['skip'])) {
803
            $qb->skip($mapping['skip']);
804
        }
805
806 17
        if (! empty($hints[Query::HINT_READ_PREFERENCE])) {
807
            $qb->setReadPreference($hints[Query::HINT_READ_PREFERENCE]);
808
        }
809
810 17
        foreach ($mapping['prime'] as $field) {
811 4
            $qb->field($field)->prime(true);
812
        }
813
814 17
        return $qb->getQuery();
815
    }
816
817 5
    private function loadReferenceManyWithRepositoryMethod(PersistentCollectionInterface $collection) : void
818
    {
819 5
        $cursor    = $this->createReferenceManyWithRepositoryMethodCursor($collection);
820 5
        $mapping   = $collection->getMapping();
821 5
        $documents = $cursor->toArray();
822 5
        foreach ($documents as $key => $obj) {
823 5
            if (CollectionHelper::isHash($mapping['strategy'])) {
824 1
                $collection->set($key, $obj);
825
            } else {
826 4
                $collection->add($obj);
827
            }
828
        }
829 5
    }
830
831 5
    public function createReferenceManyWithRepositoryMethodCursor(PersistentCollectionInterface $collection) : Iterator
832
    {
833 5
        $mapping          = $collection->getMapping();
834 5
        $repositoryMethod = $mapping['repositoryMethod'];
835 5
        $cursor           = $this->dm->getRepository($mapping['targetDocument'])
836 5
            ->$repositoryMethod($collection->getOwner());
837
838 5
        if (! $cursor instanceof Iterator) {
839
            throw new BadMethodCallException(sprintf('Expected repository method %s to return an iterable object', $repositoryMethod));
840
        }
841
842 5
        if (! empty($mapping['prime'])) {
843 1
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
844 1
            $primers         = array_combine($mapping['prime'], array_fill(0, count($mapping['prime']), true));
845 1
            $class           = $this->dm->getClassMetadata($mapping['targetDocument']);
846
847 1
            assert(is_array($primers));
848
849 1
            $cursor = new PrimingIterator($cursor, $class, $referencePrimer, $primers, $collection->getHints());
850
        }
851
852 5
        return $cursor;
853
    }
854
855
    /**
856
     * Prepare a projection array by converting keys, which are PHP property
857
     * names, to MongoDB field names.
858
     */
859 15
    public function prepareProjection(array $fields) : array
860
    {
861 15
        $preparedFields = [];
862
863 15
        foreach ($fields as $key => $value) {
864 15
            $preparedFields[$this->prepareFieldName($key)] = $value;
865
        }
866
867 15
        return $preparedFields;
868
    }
869
870
    /**
871
     * @param int|string $sort
872
     *
873
     * @return int|string|null
874
     */
875 26
    private function getSortDirection($sort)
876
    {
877 26
        switch (strtolower((string) $sort)) {
878 26
            case 'desc':
879 15
                return -1;
880
881 23
            case 'asc':
882 13
                return 1;
883
        }
884
885 13
        return $sort;
886
    }
887
888
    /**
889
     * Prepare a sort specification array by converting keys to MongoDB field
890
     * names and changing direction strings to int.
891
     */
892 142
    public function prepareSort(array $fields) : array
893
    {
894 142
        $sortFields = [];
895
896 142
        foreach ($fields as $key => $value) {
897 26
            if (is_array($value)) {
898 1
                $sortFields[$this->prepareFieldName($key)] = $value;
899
            } else {
900 26
                $sortFields[$this->prepareFieldName($key)] = $this->getSortDirection($value);
901
            }
902
        }
903
904 142
        return $sortFields;
905
    }
906
907
    /**
908
     * Prepare a mongodb field name and convert the PHP property names to
909
     * MongoDB field names.
910
     */
911 462
    public function prepareFieldName(string $fieldName) : string
912
    {
913 462
        $fieldNames = $this->prepareQueryElement($fieldName, null, null, false);
914
915 462
        return $fieldNames[0][0];
916
    }
917
918
    /**
919
     * Adds discriminator criteria to an already-prepared query.
920
     *
921
     * If the class we're querying has a discriminator field set, we add all
922
     * possible discriminator values to the query. The list of possible
923
     * discriminator values is based on the discriminatorValue of the class
924
     * itself as well as those of all its subclasses.
925
     *
926
     * This method should be used once for query criteria and not be used for
927
     * nested expressions. It should be called before
928
     * {@link DocumentPerister::addFilterToPreparedQuery()}.
929
     */
930 524
    public function addDiscriminatorToPreparedQuery(array $preparedQuery) : array
931
    {
932 524
        if (isset($preparedQuery[$this->class->discriminatorField]) || $this->class->discriminatorField === null) {
933 501
            return $preparedQuery;
934
        }
935
936 32
        $discriminatorValues = $this->getClassDiscriminatorValues($this->class);
937
938 32
        if ($discriminatorValues === []) {
939 1
            return $preparedQuery;
940
        }
941
942 32
        if (count($discriminatorValues) === 1) {
943 21
            $preparedQuery[$this->class->discriminatorField] = $discriminatorValues[0];
944
        } else {
945 14
            $preparedQuery[$this->class->discriminatorField] = ['$in' => $discriminatorValues];
946
        }
947
948 32
        return $preparedQuery;
949
950
        /* If the class has a discriminator field, which is not already in the
951
         * criteria, inject it now. The field/values need no preparation.
952
         */
953
        if ($this->class->hasDiscriminator() && ! isset($preparedQuery[$this->class->discriminatorField])) {
0 ignored issues
show
Unused Code introduced by
if ($this->class->hasDis...minatorValues); } } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
954
            $discriminatorValues = $this->getClassDiscriminatorValues($this->class);
955
            if (count($discriminatorValues) === 1) {
956
                $preparedQuery[$this->class->discriminatorField] = $discriminatorValues[0];
957
            } else {
958
                $preparedQuery[$this->class->discriminatorField] = ['$in' => $discriminatorValues];
959
            }
960
        }
961
962
        return $preparedQuery;
963
    }
964
965
    /**
966
     * Adds filter criteria to an already-prepared query.
967
     *
968
     * This method should be used once for query criteria and not be used for
969
     * nested expressions. It should be called after
970
     * {@link DocumentPerister::addDiscriminatorToPreparedQuery()}.
971
     */
972 525
    public function addFilterToPreparedQuery(array $preparedQuery) : array
973
    {
974
        /* If filter criteria exists for this class, prepare it and merge
975
         * over the existing query.
976
         *
977
         * @todo Consider recursive merging in case the filter criteria and
978
         * prepared query both contain top-level $and/$or operators.
979
         */
980 525
        $filterCriteria = $this->dm->getFilterCollection()->getFilterCriteria($this->class);
981 525
        if ($filterCriteria) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filterCriteria of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
982 18
            $preparedQuery = $this->cm->merge($preparedQuery, $this->prepareQueryOrNewObj($filterCriteria));
983
        }
984
985 525
        return $preparedQuery;
986
    }
987
988
    /**
989
     * Prepares the query criteria or new document object.
990
     *
991
     * PHP field names and types will be converted to those used by MongoDB.
992
     */
993 599
    public function prepareQueryOrNewObj(array $query, bool $isNewObj = false) : array
994
    {
995 599
        $preparedQuery = [];
996
997 599
        foreach ($query as $key => $value) {
998
            // Recursively prepare logical query clauses
999 555
            if (in_array($key, ['$and', '$or', '$nor'], true) && is_array($value)) {
1000 20
                foreach ($value as $k2 => $v2) {
1001 20
                    $preparedQuery[$key][$k2] = $this->prepareQueryOrNewObj($v2, $isNewObj);
1002
                }
1003 20
                continue;
1004
            }
1005
1006 555
            if (isset($key[0]) && $key[0] === '$' && is_array($value)) {
1007 74
                $preparedQuery[$key] = $this->prepareQueryOrNewObj($value, $isNewObj);
1008 74
                continue;
1009
            }
1010
1011 555
            $preparedQueryElements = $this->prepareQueryElement((string) $key, $value, null, true, $isNewObj);
1012 555
            foreach ($preparedQueryElements as [$preparedKey, $preparedValue]) {
1013 555
                $preparedQuery[$preparedKey] = is_array($preparedValue)
0 ignored issues
show
Bug introduced by
The variable $preparedKey does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $preparedValue does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1014 153
                    ? array_map('\Doctrine\ODM\MongoDB\Types\Type::convertPHPToDatabaseValue', $preparedValue)
1015 504
                    : Type::convertPHPToDatabaseValue($preparedValue);
1016
            }
1017
        }
1018
1019 599
        return $preparedQuery;
1020
    }
1021
1022
    /**
1023
     * Prepares a query value and converts the PHP value to the database value
1024
     * if it is an identifier.
1025
     *
1026
     * It also handles converting $fieldName to the database name if they are
1027
     * different.
1028
     *
1029
     * @param mixed $value
1030
     */
1031 977
    private function prepareQueryElement(string $fieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false) : array
1032
    {
1033 977
        $class = $class ?? $this->class;
1034
1035
        // @todo Consider inlining calls to ClassMetadata methods
1036
1037
        // Process all non-identifier fields by translating field names
1038 977
        if ($class->hasField($fieldName) && ! $class->isIdentifier($fieldName)) {
1039 281
            $mapping   = $class->fieldMappings[$fieldName];
1040 281
            $fieldName = $mapping['name'];
1041
1042 281
            if (! $prepareValue) {
1043 77
                return [[$fieldName, $value]];
1044
            }
1045
1046
            // Prepare mapped, embedded objects
1047 214
            if (! empty($mapping['embedded']) && is_object($value) &&
1048 214
                ! $this->dm->getMetadataFactory()->isTransient(get_class($value))) {
1049 3
                return [[$fieldName, $this->pb->prepareEmbeddedDocumentValue($mapping, $value)]];
1050
            }
1051
1052 212
            if (! empty($mapping['reference']) && is_object($value) && ! ($value instanceof ObjectId)) {
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\ObjectId does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1053
                try {
1054 14
                    return $this->prepareReference($fieldName, $value, $mapping, $inNewObj);
1055 1
                } catch (MappingException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Common\Persiste...apping\MappingException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1056
                    // do nothing in case passed object is not mapped document
1057
                }
1058
            }
1059
1060
            // No further preparation unless we're dealing with a simple reference
1061 199
            if (empty($mapping['reference']) || $mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID || empty((array) $value)) {
1062 132
                return [[$fieldName, $value]];
1063
            }
1064
1065
            // Additional preparation for one or more simple reference values
1066 94
            $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1067
1068 94
            if (! is_array($value)) {
1069 90
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1070
            }
1071
1072
            // Objects without operators or with DBRef fields can be converted immediately
1073 6
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1074 3
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1075
            }
1076
1077 6
            return [[$fieldName, $this->prepareQueryExpression($value, $targetClass)]];
1078
        }
1079
1080
        // Process identifier fields
1081 867
        if (($class->hasField($fieldName) && $class->isIdentifier($fieldName)) || $fieldName === '_id') {
1082 357
            $fieldName = '_id';
1083
1084 357
            if (! $prepareValue) {
1085 42
                return [[$fieldName, $value]];
1086
            }
1087
1088 318
            if (! is_array($value)) {
1089 292
                return [[$fieldName, $class->getDatabaseIdentifierValue($value)]];
1090
            }
1091
1092
            // Objects without operators or with DBRef fields can be converted immediately
1093 60
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1094 6
                return [[$fieldName, $class->getDatabaseIdentifierValue($value)]];
1095
            }
1096
1097 55
            return [[$fieldName, $this->prepareQueryExpression($value, $class)]];
1098
        }
1099
1100
        // No processing for unmapped, non-identifier, non-dotted field names
1101 611
        if (strpos($fieldName, '.') === false) {
1102 463
            return [[$fieldName, $value]];
1103
        }
1104
1105
        /* Process "fieldName.objectProperty" queries (on arrays or objects).
1106
         *
1107
         * We can limit parsing here, since at most three segments are
1108
         * significant: "fieldName.objectProperty" with an optional index or key
1109
         * for collections stored as either BSON arrays or objects.
1110
         */
1111 160
        $e = explode('.', $fieldName, 4);
1112
1113
        // No further processing for unmapped fields
1114 160
        if (! isset($class->fieldMappings[$e[0]])) {
1115 6
            return [[$fieldName, $value]];
1116
        }
1117
1118 155
        $mapping = $class->fieldMappings[$e[0]];
1119 155
        $e[0]    = $mapping['name'];
1120
1121
        // Hash and raw fields will not be prepared beyond the field name
1122 155
        if ($mapping['type'] === Type::HASH || $mapping['type'] === Type::RAW) {
1123 1
            $fieldName = implode('.', $e);
1124
1125 1
            return [[$fieldName, $value]];
1126
        }
1127
1128 154
        if ($mapping['type'] === 'many' && CollectionHelper::isHash($mapping['strategy'])
1129 154
                && isset($e[2])) {
1130 1
            $objectProperty       = $e[2];
1131 1
            $objectPropertyPrefix = $e[1] . '.';
1132 1
            $nextObjectProperty   = implode('.', array_slice($e, 3));
1133 153
        } elseif ($e[1] !== '$') {
1134 152
            $fieldName            = $e[0] . '.' . $e[1];
1135 152
            $objectProperty       = $e[1];
1136 152
            $objectPropertyPrefix = '';
1137 152
            $nextObjectProperty   = implode('.', array_slice($e, 2));
1138 1
        } elseif (isset($e[2])) {
1139 1
            $fieldName            = $e[0] . '.' . $e[1] . '.' . $e[2];
1140 1
            $objectProperty       = $e[2];
1141 1
            $objectPropertyPrefix = $e[1] . '.';
1142 1
            $nextObjectProperty   = implode('.', array_slice($e, 3));
1143
        } else {
1144 1
            $fieldName = $e[0] . '.' . $e[1];
1145
1146 1
            return [[$fieldName, $value]];
1147
        }
1148
1149
        // No further processing for fields without a targetDocument mapping
1150 154
        if (! isset($mapping['targetDocument'])) {
1151 5
            if ($nextObjectProperty) {
1152
                $fieldName .= '.' . $nextObjectProperty;
1153
            }
1154
1155 5
            return [[$fieldName, $value]];
1156
        }
1157
1158 149
        $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1159
1160
        // No further processing for unmapped targetDocument fields
1161 149
        if (! $targetClass->hasField($objectProperty)) {
1162 26
            if ($nextObjectProperty) {
1163
                $fieldName .= '.' . $nextObjectProperty;
1164
            }
1165
1166 26
            return [[$fieldName, $value]];
1167
        }
1168
1169 128
        $targetMapping      = $targetClass->getFieldMapping($objectProperty);
1170 128
        $objectPropertyIsId = $targetClass->isIdentifier($objectProperty);
1171
1172
        // Prepare DBRef identifiers or the mapped field's property path
1173 128
        $fieldName = $objectPropertyIsId && ! empty($mapping['reference']) && $mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID
1174 108
            ? ClassMetadata::getReferenceFieldName($mapping['storeAs'], $e[0])
1175 128
            : $e[0] . '.' . $objectPropertyPrefix . $targetMapping['name'];
1176
1177
        // Process targetDocument identifier fields
1178 128
        if ($objectPropertyIsId) {
1179 109
            if (! $prepareValue) {
1180 7
                return [[$fieldName, $value]];
1181
            }
1182
1183 102
            if (! is_array($value)) {
1184 88
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1185
            }
1186
1187
            // Objects without operators or with DBRef fields can be converted immediately
1188 16
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1189 6
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1190
            }
1191
1192 16
            return [[$fieldName, $this->prepareQueryExpression($value, $targetClass)]];
1193
        }
1194
1195
        /* The property path may include a third field segment, excluding the
1196
         * collection item pointer. If present, this next object property must
1197
         * be processed recursively.
1198
         */
1199 19
        if ($nextObjectProperty) {
1200
            // Respect the targetDocument's class metadata when recursing
1201 16
            $nextTargetClass = isset($targetMapping['targetDocument'])
1202 10
                ? $this->dm->getClassMetadata($targetMapping['targetDocument'])
1203 16
                : null;
1204
1205 16
            if (empty($targetMapping['reference'])) {
1206 14
                $fieldNames = $this->prepareQueryElement($nextObjectProperty, $value, $nextTargetClass, $prepareValue);
1207
            } else {
1208
                // No recursive processing for references as most probably somebody is querying DBRef or alike
1209 4
                if ($nextObjectProperty[0] !== '$' && in_array($targetMapping['storeAs'], [ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB, ClassMetadata::REFERENCE_STORE_AS_DB_REF])) {
1210 1
                    $nextObjectProperty = '$' . $nextObjectProperty;
1211
                }
1212 4
                $fieldNames = [[$nextObjectProperty, $value]];
1213
            }
1214
1215
            return array_map(static function ($preparedTuple) use ($fieldName) {
1216 16
                [$key, $value] = $preparedTuple;
0 ignored issues
show
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1217
1218 16
                return [$fieldName . '.' . $key, $value];
1219 16
            }, $fieldNames);
1220
        }
1221
1222 5
        return [[$fieldName, $value]];
1223
    }
1224
1225 77
    private function prepareQueryExpression(array $expression, ClassMetadata $class) : array
1226
    {
1227 77
        foreach ($expression as $k => $v) {
1228
            // Ignore query operators whose arguments need no type conversion
1229 77
            if (in_array($k, ['$exists', '$type', '$mod', '$size'])) {
1230 16
                continue;
1231
            }
1232
1233
            // Process query operators whose argument arrays need type conversion
1234 77
            if (in_array($k, ['$in', '$nin', '$all']) && is_array($v)) {
1235 75
                foreach ($v as $k2 => $v2) {
1236 75
                    $expression[$k][$k2] = $class->getDatabaseIdentifierValue($v2);
1237
                }
1238 75
                continue;
1239
            }
1240
1241
            // Recursively process expressions within a $not operator
1242 18
            if ($k === '$not' && is_array($v)) {
1243 15
                $expression[$k] = $this->prepareQueryExpression($v, $class);
1244 15
                continue;
1245
            }
1246
1247 18
            $expression[$k] = $class->getDatabaseIdentifierValue($v);
1248
        }
1249
1250 77
        return $expression;
1251
    }
1252
1253
    /**
1254
     * Checks whether the value has DBRef fields.
1255
     *
1256
     * This method doesn't check if the the value is a complete DBRef object,
1257
     * although it should return true for a DBRef. Rather, we're checking that
1258
     * the value has one or more fields for a DBref. In practice, this could be
1259
     * $elemMatch criteria for matching a DBRef.
1260
     *
1261
     * @param mixed $value
1262
     */
1263 78
    private function hasDBRefFields($value) : bool
1264
    {
1265 78
        if (! is_array($value) && ! is_object($value)) {
1266
            return false;
1267
        }
1268
1269 78
        if (is_object($value)) {
1270
            $value = get_object_vars($value);
1271
        }
1272
1273 78
        foreach ($value as $key => $_) {
1274 78
            if ($key === '$ref' || $key === '$id' || $key === '$db') {
1275 4
                return true;
1276
            }
1277
        }
1278
1279 77
        return false;
1280
    }
1281
1282
    /**
1283
     * Checks whether the value has query operators.
1284
     *
1285
     * @param mixed $value
1286
     */
1287 82
    private function hasQueryOperators($value) : bool
1288
    {
1289 82
        if (! is_array($value) && ! is_object($value)) {
1290
            return false;
1291
        }
1292
1293 82
        if (is_object($value)) {
1294
            $value = get_object_vars($value);
1295
        }
1296
1297 82
        foreach ($value as $key => $_) {
1298 82
            if (isset($key[0]) && $key[0] === '$') {
1299 78
                return true;
1300
            }
1301
        }
1302
1303 11
        return false;
1304
    }
1305
1306
    /**
1307
     * Returns the list of discriminator values for the given ClassMetadata
1308
     */
1309 32
    private function getClassDiscriminatorValues(ClassMetadata $metadata) : array
1310
    {
1311 32
        $discriminatorValues = [];
1312
1313 32
        if ($metadata->discriminatorValue !== null) {
1314 29
            $discriminatorValues[] = $metadata->discriminatorValue;
1315
        }
1316
1317 32
        foreach ($metadata->subClasses as $className) {
1318 12
            $key = array_search($className, $metadata->discriminatorMap);
1319 12
            if (! $key) {
1320
                continue;
1321
            }
1322
1323 12
            $discriminatorValues[] = $key;
1324
        }
1325
1326
        // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
1327 32
        if ($metadata->defaultDiscriminatorValue && in_array($metadata->defaultDiscriminatorValue, $discriminatorValues)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->defaultDiscriminatorValue of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1328 3
            $discriminatorValues[] = null;
1329
        }
1330
1331 32
        return $discriminatorValues;
1332
    }
1333
1334 595
    private function handleCollections(object $document, array $options) : void
1335
    {
1336
        // Collection deletions (deletions of complete collections)
1337 595
        $collections = [];
1338 595
        foreach ($this->uow->getScheduledCollections($document) as $coll) {
1339 113
            if (! $this->uow->isCollectionScheduledForDeletion($coll)) {
1340 102
                continue;
1341
            }
1342
1343 33
            $collections[] = $coll;
1344
        }
1345 595
        if (! empty($collections)) {
1346 33
            $this->cp->delete($document, $collections, $options);
1347
        }
1348
        // Collection updates (deleteRows, updateRows, insertRows)
1349 595
        $collections = [];
1350 595
        foreach ($this->uow->getScheduledCollections($document) as $coll) {
1351 113
            if (! $this->uow->isCollectionScheduledForUpdate($coll)) {
1352 29
                continue;
1353
            }
1354
1355 105
            $collections[] = $coll;
1356
        }
1357 595
        if (! empty($collections)) {
1358 105
            $this->cp->update($document, $collections, $options);
1359
        }
1360
        // Take new snapshots from visited collections
1361 595
        foreach ($this->uow->getVisitedCollections($document) as $coll) {
1362 252
            $coll->takeSnapshot();
1363
        }
1364 595
    }
1365
1366
    /**
1367
     * If the document is new, ignore shard key field value, otherwise throw an
1368
     * exception. Also, shard key field should be present in actual document
1369
     * data.
1370
     *
1371
     * @throws MongoDBException
1372
     */
1373 10
    private function guardMissingShardKey(object $document, string $shardKeyField, array $actualDocumentData) : void
1374
    {
1375 10
        $dcs      = $this->uow->getDocumentChangeSet($document);
1376 10
        $isUpdate = $this->uow->isScheduledForUpdate($document);
1377
1378 10
        $fieldMapping = $this->class->getFieldMappingByDbFieldName($shardKeyField);
1379 10
        $fieldName    = $fieldMapping['fieldName'];
1380
1381 10
        if ($isUpdate && isset($dcs[$fieldName]) && $dcs[$fieldName][0] !== $dcs[$fieldName][1]) {
1382 2
            throw MongoDBException::shardKeyFieldCannotBeChanged($shardKeyField, $this->class->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1383
        }
1384
1385 8
        if (! isset($actualDocumentData[$fieldName])) {
1386
            throw MongoDBException::shardKeyFieldMissing($shardKeyField, $this->class->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1387
        }
1388 8
    }
1389
1390
    /**
1391
     * Get shard key aware query for single document.
1392
     */
1393 310
    private function getQueryForDocument(object $document) : array
1394
    {
1395 310
        $id = $this->uow->getDocumentIdentifier($document);
1396 310
        $id = $this->class->getDatabaseIdentifierValue($id);
1397
1398 310
        $shardKeyQueryPart = $this->getShardKeyQuery($document);
1399 308
        return array_merge(['_id' => $id], $shardKeyQueryPart);
1400
    }
1401
1402 606
    private function getWriteOptions(array $options = []) : array
1403
    {
1404 606
        $defaultOptions  = $this->dm->getConfiguration()->getDefaultCommitOptions();
1405 606
        $documentOptions = [];
1406 606
        if ($this->class->hasWriteConcern()) {
1407 9
            $documentOptions['w'] = $this->class->getWriteConcern();
1408
        }
1409
1410 606
        return array_merge($defaultOptions, $documentOptions, $options);
1411
    }
1412
1413 15
    private function prepareReference(string $fieldName, $value, array $mapping, bool $inNewObj) : array
1414
    {
1415 15
        $reference = $this->dm->createReference($value, $mapping);
1416 14
        if ($inNewObj || $mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_ID) {
1417 8
            return [[$fieldName, $reference]];
1418
        }
1419
1420 6
        switch ($mapping['storeAs']) {
1421
            case ClassMetadata::REFERENCE_STORE_AS_REF:
1422
                $keys = ['id' => true];
1423
                break;
1424
1425
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
1426
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
1427 6
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
1428
1429 6
                if ($mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
1430 5
                    unset($keys['$db']);
1431
                }
1432
1433 6
                if (isset($mapping['targetDocument'])) {
1434 4
                    unset($keys['$ref'], $keys['$db']);
1435
                }
1436 6
                break;
1437
1438
            default:
1439
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $mapping['storeAs']));
1440
        }
1441
1442 6
        if ($mapping['type'] === 'many') {
1443 2
            return [[$fieldName, ['$elemMatch' => array_intersect_key($reference, $keys)]]];
1444
        }
1445
1446 4
        return array_map(
1447
            static function ($key) use ($reference, $fieldName) {
1448 4
                return [$fieldName . '.' . $key, $reference[$key]];
1449 4
            },
1450 4
            array_keys($keys)
1451
        );
1452
    }
1453
}
1454