Completed
Pull Request — master (#2118)
by Maciej
30:32 queued 05:27
created

DocumentPersister::hasQueryOperators()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 7.7656

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 6
cts 8
cp 0.75
rs 8.8333
c 0
b 0
f 0
cc 7
nc 7
nop 1
crap 7.7656
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Persisters;
6
7
use BadMethodCallException;
8
use DateTime;
9
use DateTimeImmutable;
10
use Doctrine\Common\Persistence\Mapping\MappingException;
11
use Doctrine\ODM\MongoDB\DocumentManager;
12
use Doctrine\ODM\MongoDB\Hydrator\HydratorException;
13
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
14
use Doctrine\ODM\MongoDB\Iterator\CachingIterator;
15
use Doctrine\ODM\MongoDB\Iterator\HydratingIterator;
16
use Doctrine\ODM\MongoDB\Iterator\Iterator;
17
use Doctrine\ODM\MongoDB\Iterator\PrimingIterator;
18
use Doctrine\ODM\MongoDB\LockException;
19
use Doctrine\ODM\MongoDB\LockMode;
20
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
21
use Doctrine\ODM\MongoDB\MongoDBException;
22
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionException;
23
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
24
use Doctrine\ODM\MongoDB\Query\CriteriaMerger;
25
use Doctrine\ODM\MongoDB\Query\Query;
26
use Doctrine\ODM\MongoDB\Query\ReferencePrimer;
27
use Doctrine\ODM\MongoDB\Types\Type;
28
use Doctrine\ODM\MongoDB\UnitOfWork;
29
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
30
use InvalidArgumentException;
31
use MongoDB\BSON\ObjectId;
32
use MongoDB\Collection;
33
use MongoDB\Driver\Cursor;
34
use MongoDB\Driver\Exception\Exception as DriverException;
35
use MongoDB\Driver\Exception\WriteException;
36
use MongoDB\GridFS\Bucket;
37
use ProxyManager\Proxy\GhostObjectInterface;
38
use stdClass;
39
use function array_combine;
40
use function array_fill;
41
use function array_intersect_key;
42
use function array_keys;
43
use function array_map;
44
use function array_merge;
45
use function array_search;
46
use function array_slice;
47
use function array_values;
48
use function assert;
49
use function count;
50
use function explode;
51
use function get_class;
52
use function get_object_vars;
53
use function gettype;
54
use function implode;
55
use function in_array;
56
use function is_array;
57
use function is_object;
58
use function is_scalar;
59
use function is_string;
60
use function max;
61
use function spl_object_hash;
62
use function sprintf;
63
use function strpos;
64
use function strtolower;
65
66
/**
67
 * The DocumentPersister is responsible for persisting documents.
68
 *
69
 * @internal
70
 */
71
final class DocumentPersister
72
{
73
    /** @var PersistenceBuilder */
74
    private $pb;
75
76
    /** @var DocumentManager */
77
    private $dm;
78
79
    /** @var UnitOfWork */
80
    private $uow;
81
82
    /** @var ClassMetadata */
83
    private $class;
84
85
    /** @var Collection|null */
86
    private $collection;
87
88
    /** @var Bucket|null */
89
    private $bucket;
90
91
    /**
92
     * Array of queued inserts for the persister to insert.
93
     *
94
     * @var array
95
     */
96
    private $queuedInserts = [];
97
98
    /**
99
     * Array of queued inserts for the persister to insert.
100
     *
101
     * @var array
102
     */
103
    private $queuedUpserts = [];
104
105
    /** @var CriteriaMerger */
106
    private $cm;
107
108
    /** @var CollectionPersister */
109
    private $cp;
110
111
    /** @var HydratorFactory */
112
    private $hydratorFactory;
113 1206
114
    public function __construct(
115
        PersistenceBuilder $pb,
116
        DocumentManager $dm,
117
        UnitOfWork $uow,
118
        HydratorFactory $hydratorFactory,
119
        ClassMetadata $class,
120
        ?CriteriaMerger $cm = null
121 1206
    ) {
122 1206
        $this->pb              = $pb;
123 1206
        $this->dm              = $dm;
124 1206
        $this->cm              = $cm ?: new CriteriaMerger();
125 1206
        $this->uow             = $uow;
126 1206
        $this->hydratorFactory = $hydratorFactory;
127 1206
        $this->class           = $class;
128
        $this->cp              = $this->uow->getCollectionPersister();
129 1206
130 95
        if ($class->isEmbeddedDocument || $class->isQueryResultDocument) {
131
            return;
132
        }
133 1203
134
        $this->collection = $dm->getDocumentCollection($class->name);
135 1203
136 1190
        if (! $class->isFile) {
137
            return;
138
        }
139 21
140 21
        $this->bucket = $dm->getDocumentBucket($class->name);
141
    }
142
143
    public function getInserts() : array
144
    {
145
        return $this->queuedInserts;
146
    }
147
148
    public function isQueuedForInsert(object $document) : bool
149
    {
150
        return isset($this->queuedInserts[spl_object_hash($document)]);
151
    }
152
153
    /**
154
     * Adds a document to the queued insertions.
155
     * The document remains queued until {@link executeInserts} is invoked.
156 533
     */
157
    public function addInsert(object $document) : void
158 533
    {
159 533
        $this->queuedInserts[spl_object_hash($document)] = $document;
160
    }
161
162
    public function getUpserts() : array
163
    {
164
        return $this->queuedUpserts;
165
    }
166
167
    public function isQueuedForUpsert(object $document) : bool
168
    {
169
        return isset($this->queuedUpserts[spl_object_hash($document)]);
170
    }
171
172
    /**
173
     * Adds a document to the queued upserts.
174
     * The document remains queued until {@link executeUpserts} is invoked.
175 85
     */
176
    public function addUpsert(object $document) : void
177 85
    {
178 85
        $this->queuedUpserts[spl_object_hash($document)] = $document;
179
    }
180
181
    /**
182
     * Gets the ClassMetadata instance of the document class this persister is
183
     * used for.
184
     */
185
    public function getClassMetadata() : ClassMetadata
186
    {
187
        return $this->class;
188
    }
189
190
    /**
191
     * Executes all queued document insertions.
192
     *
193
     * Queued documents without an ID will inserted in a batch and queued
194
     * documents with an ID will be upserted individually.
195
     *
196
     * If no inserts are queued, invoking this method is a NOOP.
197
     *
198
     * @throws DriverException
199 533
     */
200
    public function executeInserts(array $options = []) : void
201 533
    {
202
        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...
203
            return;
204
        }
205 533
206 533
        $inserts = [];
207 533
        $options = $this->getWriteOptions($options);
208 533
        foreach ($this->queuedInserts as $oid => $document) {
209
            $data = $this->pb->prepareInsertData($document);
210
211 522
            // Set the initial version for each insert
212 40
            if ($this->class->isVersioned) {
213 40
                $versionMapping = $this->class->fieldMappings[$this->class->versionField];
214 40
                $nextVersion    = null;
215 38
                if ($versionMapping['type'] === Type::INT || $versionMapping['type'] === Type::INTEGER) {
216 38
                    $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
217 2
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
218 2
                } elseif ($versionMapping['type'] === Type::DATE || $versionMapping['type'] === Type::DATE_IMMUTABLE) {
219 2
                    $nextVersionDateTime = $versionMapping['type'] === Type::DATE ? new DateTime() : new DateTimeImmutable();
220 2
                    $nextVersion         = Type::convertPHPToDatabaseValue($nextVersionDateTime);
221
                    $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
222 40
                }
223
                $data[$versionMapping['name']] = $nextVersion;
224
            }
225 522
226
            $inserts[] = $data;
227
        }
228 522
229
        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...
230 522
            try {
231 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...
232 6
                $this->collection->insertMany($inserts, $options);
233 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...
234 6
                $this->queuedInserts = [];
235
                throw $e;
236
            }
237
        }
238
239
        /* All collections except for ones using addToSet have already been
240
         * saved. We have left these to be handled separately to avoid checking
241
         * collection for uniqueness on PHP side.
242 522
         */
243 522
        foreach ($this->queuedInserts as $document) {
244
            $this->handleCollections($document, $options);
245
        }
246 522
247 522
        $this->queuedInserts = [];
248
    }
249
250
    /**
251
     * Executes all queued document upserts.
252
     *
253
     * Queued documents with an ID are upserted individually.
254
     *
255
     * If no upserts are queued, invoking this method is a NOOP.
256 85
     */
257
    public function executeUpserts(array $options = []) : void
258 85
    {
259
        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...
260
            return;
261
        }
262 85
263 85
        $options = $this->getWriteOptions($options);
264
        foreach ($this->queuedUpserts as $oid => $document) {
265 85
            try {
266 85
                $this->executeUpsert($document, $options);
267 85
                $this->handleCollections($document, $options);
268
                unset($this->queuedUpserts[$oid]);
269
            } 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...
270
                unset($this->queuedUpserts[$oid]);
271
                throw $e;
272
            }
273 85
        }
274
    }
275
276
    /**
277
     * Executes a single upsert in {@link executeUpserts}
278 85
     */
279
    private function executeUpsert(object $document, array $options) : void
280 85
    {
281 85
        $options['upsert'] = true;
282
        $criteria          = $this->getQueryForDocument($document);
283 85
284
        $data = $this->pb->prepareUpsertData($document);
285
286 85
        // Set the initial version for each upsert
287 3
        if ($this->class->isVersioned) {
288 3
            $versionMapping = $this->class->fieldMappings[$this->class->versionField];
289 3
            $nextVersion    = null;
290 2
            if ($versionMapping['type'] === Type::INT || $versionMapping === Type::INTEGER) {
291 2
                $nextVersion = max(1, (int) $this->class->reflFields[$this->class->versionField]->getValue($document));
292 1
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
293 1
            } elseif ($versionMapping['type'] === Type::DATE || $versionMapping['type'] === Type::DATE_IMMUTABLE) {
294 1
                $nextVersionDateTime = $versionMapping['type'] === Type::DATE ? new DateTime() : new DateTimeImmutable();
295 1
                $nextVersion         = Type::convertPHPToDatabaseValue($nextVersionDateTime);
296
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersionDateTime);
297 3
            }
298
            $data['$set'][$versionMapping['name']] = $nextVersion;
299
        }
300 85
301 85
        foreach (array_keys($criteria) as $field) {
302 85
            unset($data['$set'][$field]);
303 85
            unset($data['$inc'][$field]);
304
            unset($data['$setOnInsert'][$field]);
305
        }
306
307 85
        // Do not send empty update operators
308 85
        foreach (['$set', '$inc', '$setOnInsert'] as $operator) {
309 70
            if (! empty($data[$operator])) {
310
                continue;
311
            }
312 85
313
            unset($data[$operator]);
314
        }
315
316
        /* If there are no modifiers remaining, we're upserting a document with
317
         * an identifier as its only field. Since a document with the identifier
318
         * may already exist, the desired behavior is "insert if not exists" and
319
         * NOOP otherwise. MongoDB 2.6+ does not allow empty modifiers, so $set
320
         * the identifier to the same value in our criteria.
321
         *
322
         * This will fail for versions before MongoDB 2.6, which require an
323
         * empty $set modifier. The best we can do (without attempting to check
324
         * server versions in advance) is attempt the 2.6+ behavior and retry
325
         * after the relevant exception.
326
         *
327
         * See: https://jira.mongodb.org/browse/SERVER-12266
328 85
         */
329 16
        if (empty($data)) {
330 16
            $retry = true;
331
            $data  = ['$set' => ['_id' => $criteria['_id']]];
332
        }
333
334 85
        try {
335 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...
336
            $this->collection->updateOne($criteria, $data, $options);
337 85
338
            return;
339
        } 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...
340
            if (empty($retry) || strpos($e->getMessage(), 'Mod on _id not allowed') === false) {
341
                throw $e;
342
            }
343
        }
344
345
        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...
346
        $this->collection->updateOne($criteria, ['$set' => new stdClass()], $options);
347
    }
348
349
    /**
350
     * Updates the already persisted document if it has any new changesets.
351
     *
352
     * @throws LockException
353 234
     */
354
    public function update(object $document, array $options = []) : void
355 234
    {
356
        $update = $this->pb->prepareUpdateData($document);
357 234
358
        $query = $this->getQueryForDocument($document);
359 232
360 232
        foreach (array_keys($query) as $field) {
361
            unset($update['$set'][$field]);
362
        }
363 232
364 101
        if (empty($update['$set'])) {
365
            unset($update['$set']);
366
        }
367
368
        // Include versioning logic to set the new version value in the database
369
        // and to ensure the version has not changed since this document object instance
370 232
        // was fetched from the database
371 232
        $nextVersion = null;
372 33
        if ($this->class->isVersioned) {
373 33
            $versionMapping = $this->class->fieldMappings[$this->class->versionField];
374 33
            $currentVersion = $this->class->reflFields[$this->class->versionField]->getValue($document);
375 30
            if ($versionMapping['type'] === Type::INT || $versionMapping['type'] === Type::INTEGER) {
376 30
                $nextVersion                             = $currentVersion + 1;
377 30
                $update['$inc'][$versionMapping['name']] = 1;
378 3
                $query[$versionMapping['name']]          = $currentVersion;
379 3
            } elseif ($versionMapping['type'] === Type::DATE || $versionMapping['type'] === Type::DATE_IMMUTABLE) {
380 3
                $nextVersion                             = $versionMapping['type'] === Type::DATE ? new DateTime() : new DateTimeImmutable();
381 3
                $update['$set'][$versionMapping['name']] = Type::convertPHPToDatabaseValue($nextVersion);
382
                $query[$versionMapping['name']]          = Type::convertPHPToDatabaseValue($currentVersion);
383
            }
384
        }
385 232
386
        if (! empty($update)) {
387
            // Include locking logic so that if the document object in memory is currently
388 158
            // locked then it will remove it, otherwise it ensures the document is not locked.
389 11
            if ($this->class->isLockable) {
390 11
                $isLocked    = $this->class->reflFields[$this->class->lockField]->getValue($document);
391 11
                $lockMapping = $this->class->fieldMappings[$this->class->lockField];
392 2
                if ($isLocked) {
393
                    $update['$unset'] = [$lockMapping['name'] => true];
394 9
                } else {
395
                    $query[$lockMapping['name']] = ['$exists' => false];
396
                }
397
            }
398 158
399
            $options = $this->getWriteOptions($options);
400 158
401 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...
402
            $result = $this->collection->updateOne($query, $update, $options);
403 158
404 6
            if (($this->class->isVersioned || $this->class->isLockable) && $result->getModifiedCount() !== 1) {
405
                throw LockException::lockFailed($document);
406
            }
407 153
408 28
            if ($this->class->isVersioned) {
409
                $this->class->reflFields[$this->class->versionField]->setValue($document, $nextVersion);
410
            }
411
        }
412 227
413 227
        $this->handleCollections($document, $options);
414
    }
415
416
    /**
417
     * Removes document from mongo
418
     *
419
     * @throws LockException
420 36
     */
421
    public function delete(object $document, array $options = []) : void
422 36
    {
423 1
        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...
424 1
            $documentIdentifier = $this->uow->getDocumentIdentifier($document);
425
            $databaseIdentifier = $this->class->getDatabaseIdentifierValue($documentIdentifier);
426 1
427
            $this->bucket->delete($databaseIdentifier);
428 1
429
            return;
430
        }
431 35
432
        $query = $this->getQueryForDocument($document);
433 35
434 2
        if ($this->class->isLockable) {
435
            $query[$this->class->lockField] = ['$exists' => false];
436
        }
437 35
438
        $options = $this->getWriteOptions($options);
439 35
440 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...
441
        $result = $this->collection->deleteOne($query, $options);
442 35
443 2
        if (($this->class->isVersioned || $this->class->isLockable) && ! $result->getDeletedCount()) {
444
            throw LockException::lockFailed($document);
445 33
        }
446
    }
447
448
    /**
449
     * Refreshes a managed document.
450 23
     */
451
    public function refresh(object $document) : void
452 23
    {
453 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...
454 23
        $query = $this->getQueryForDocument($document);
455 23
        $data  = $this->collection->findOne($query);
456
        if ($data === null) {
457
            throw MongoDBException::cannotRefreshDocument();
458 23
        }
459 23
        $data = $this->hydratorFactory->hydrate($document, (array) $data);
460 23
        $this->uow->setOriginalDocumentData($document, $data);
461
    }
462
463
    /**
464
     * Finds a document by a set of criteria.
465
     *
466
     * If a scalar or MongoDB\BSON\ObjectId is provided for $criteria, it will
467
     * be used to match an _id value.
468
     *
469
     * @param mixed $criteria Query criteria
470
     *
471
     * @throws LockException
472
     *
473
     * @todo Check identity map? loadById method? Try to guess whether
474
     *     $criteria is the id?
475 367
     */
476
    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...
477
    {
478 367
        // TODO: remove this
479
        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...
480
            $criteria = ['_id' => $criteria];
481
        }
482 367
483 367
        $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...
484 367
        $criteria = $this->addDiscriminatorToPreparedQuery($criteria);
485
        $criteria = $this->addFilterToPreparedQuery($criteria);
486 367
487 367
        $options = [];
488 95
        if ($sort !== null) {
489
            $options['sort'] = $this->prepareSort($sort);
490 367
        }
491 367
        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...
492 367
        $result = $this->collection->findOne($criteria, $options);
493
        $result = $result !== null ? (array) $result : null;
494 367
495 1
        if ($this->class->isLockable) {
496 1
            $lockMapping = $this->class->fieldMappings[$this->class->lockField];
497 1
            if (isset($result[$lockMapping['name']]) && $result[$lockMapping['name']] === LockMode::PESSIMISTIC_WRITE) {
498
                throw LockException::lockFailed($document);
499
            }
500
        }
501 366
502 115
        if ($result === null) {
503
            return null;
504
        }
505 322
506
        return $this->createDocument($result, $document, $hints);
507
    }
508
509
    /**
510
     * Finds documents by a set of criteria.
511 24
     */
512
    public function loadAll(array $criteria = [], ?array $sort = null, ?int $limit = null, ?int $skip = null) : Iterator
513 24
    {
514 24
        $criteria = $this->prepareQueryOrNewObj($criteria);
515 24
        $criteria = $this->addDiscriminatorToPreparedQuery($criteria);
516
        $criteria = $this->addFilterToPreparedQuery($criteria);
517 24
518 24
        $options = [];
519 11
        if ($sort !== null) {
520
            $options['sort'] = $this->prepareSort($sort);
521
        }
522 24
523 10
        if ($limit !== null) {
524
            $options['limit'] = $limit;
525
        }
526 24
527 1
        if ($skip !== null) {
528
            $options['skip'] = $skip;
529
        }
530 24
531 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...
532
        $baseCursor = $this->collection->find($criteria, $options);
533 24
534
        return $this->wrapCursor($baseCursor);
535
    }
536
537
    /**
538
     * @throws MongoDBException
539 314
     */
540
    private function getShardKeyQuery(object $document) : array
541 314
    {
542 304
        if (! $this->class->isSharded()) {
543
            return [];
544
        }
545 10
546 10
        $shardKey = $this->class->getShardKey();
547 10
        $keys     = array_keys($shardKey['keys']);
548
        $data     = $this->uow->getDocumentActualData($document);
549 10
550 10
        $shardKeyQueryPart = [];
551 10
        foreach ($keys as $key) {
552 10
            assert(is_string($key));
553 10
            $mapping = $this->class->getFieldMappingByDbFieldName($key);
554
            $this->guardMissingShardKey($document, $key, $data);
555 8
556 1
            if (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
557 1
                $reference = $this->prepareReference(
558 1
                    $key,
559 1
                    $data[$mapping['fieldName']],
560 1
                    $mapping,
561
                    false
562 1
                );
563 1
                foreach ($reference as $keyValue) {
564
                    $shardKeyQueryPart[$keyValue[0]] = $keyValue[1];
565
                }
566 7
            } else {
567 7
                $value                   = Type::getType($mapping['type'])->convertToDatabaseValue($data[$mapping['fieldName']]);
568
                $shardKeyQueryPart[$key] = $value;
569
            }
570
        }
571 8
572
        return $shardKeyQueryPart;
573
    }
574
575
    /**
576
     * Wraps the supplied base cursor in the corresponding ODM class.
577 24
     */
578
    private function wrapCursor(Cursor $baseCursor) : Iterator
579 24
    {
580
        return new CachingIterator(new HydratingIterator($baseCursor, $this->dm->getUnitOfWork(), $this->class));
581
    }
582
583
    /**
584
     * Checks whether the given managed document exists in the database.
585 3
     */
586
    public function exists(object $document) : bool
587 3
    {
588 3
        $id = $this->class->getIdentifierObject($document);
589
        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...
590 3
591
        return (bool) $this->collection->findOne(['_id' => $id], ['_id']);
592
    }
593
594
    /**
595
     * Locks document by storing the lock mode on the mapped lock field.
596 5
     */
597
    public function lock(object $document, int $lockMode) : void
598 5
    {
599 5
        $id          = $this->uow->getDocumentIdentifier($document);
600 5
        $criteria    = ['_id' => $this->class->getDatabaseIdentifierValue($id)];
601 5
        $lockMapping = $this->class->fieldMappings[$this->class->lockField];
602 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...
603 5
        $this->collection->updateOne($criteria, ['$set' => [$lockMapping['name'] => $lockMode]]);
604 5
        $this->class->reflFields[$this->class->lockField]->setValue($document, $lockMode);
605
    }
606
607
    /**
608
     * Releases any lock that exists on this document.
609 1
     */
610
    public function unlock(object $document) : void
611 1
    {
612 1
        $id          = $this->uow->getDocumentIdentifier($document);
613 1
        $criteria    = ['_id' => $this->class->getDatabaseIdentifierValue($id)];
614 1
        $lockMapping = $this->class->fieldMappings[$this->class->lockField];
615 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...
616 1
        $this->collection->updateOne($criteria, ['$unset' => [$lockMapping['name'] => true]]);
617 1
        $this->class->reflFields[$this->class->lockField]->setValue($document, null);
618
    }
619
620
    /**
621
     * Creates or fills a single document object from an query result.
622
     *
623
     * @param array  $result   The query result.
624
     * @param object $document The document object to fill, if any.
625
     * @param array  $hints    Hints for document creation.
626
     *
627
     * @return object|null The filled and managed document object or NULL, if the query result is empty.
628 322
     */
629
    private function createDocument(array $result, ?object $document = null, array $hints = []) : ?object
630 322
    {
631 27
        if ($document !== null) {
632 27
            $hints[Query::HINT_REFRESH] = true;
633 27
            $id                         = $this->class->getPHPIdentifierValue($result['_id']);
634
            $this->uow->registerManaged($document, $id, $result);
635
        }
636 322
637
        return $this->uow->getOrCreateDocument($this->class->name, $result, $hints, $document);
638
    }
639
640
    /**
641
     * Loads a PersistentCollection data. Used in the initialize() method.
642 180
     */
643
    public function loadCollection(PersistentCollectionInterface $collection) : void
644 180
    {
645 180
        $mapping = $collection->getMapping();
646
        switch ($mapping['association']) {
647 127
            case ClassMetadata::EMBED_MANY:
648 126
                $this->loadEmbedManyCollection($collection);
649
                break;
650
651 76
            case ClassMetadata::REFERENCE_MANY:
652 5
                if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) {
653
                    $this->loadReferenceManyWithRepositoryMethod($collection);
654 72
                } else {
655 60
                    if ($mapping['isOwningSide']) {
656
                        $this->loadReferenceManyCollectionOwningSide($collection);
657 17
                    } else {
658
                        $this->loadReferenceManyCollectionInverseSide($collection);
659
                    }
660 75
                }
661
                break;
662 178
        }
663
    }
664 127
665
    private function loadEmbedManyCollection(PersistentCollectionInterface $collection) : void
666 127
    {
667 127
        $embeddedDocuments = $collection->getMongoData();
668 127
        $mapping           = $collection->getMapping();
669
        $owner             = $collection->getOwner();
670 127
671 75
        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...
672
            return;
673
        }
674 98
675
        if ($owner === null) {
676
            throw PersistentCollectionException::ownerRequiredToLoadCollection();
677
        }
678 98
679 98
        foreach ($embeddedDocuments as $key => $embeddedDocument) {
680 98
            $className              = $this->uow->getClassNameForAssociation($mapping, $embeddedDocument);
681 98
            $embeddedMetadata       = $this->dm->getClassMetadata($className);
682
            $embeddedDocumentObject = $embeddedMetadata->newInstance();
683 98
684 1
            if (! is_array($embeddedDocument)) {
685
                throw HydratorException::associationItemTypeMismatch(get_class($owner), $mapping['name'], $key, 'array', gettype($embeddedDocument));
686
            }
687 97
688
            $this->uow->setParentAssociation($embeddedDocumentObject, $mapping, $owner, $mapping['name'] . '.' . $key);
689 97
690 97
            $data = $this->hydratorFactory->hydrate($embeddedDocumentObject, $embeddedDocument, $collection->getHints());
691
            $id   = $data[$embeddedMetadata->identifier] ?? null;
692 97
693 96
            if (empty($collection->getHints()[Query::HINT_READ_ONLY])) {
694
                $this->uow->registerManaged($embeddedDocumentObject, $id, $data);
695 97
            }
696 25
            if (CollectionHelper::isHash($mapping['strategy'])) {
697
                $collection->set($key, $embeddedDocumentObject);
698 80
            } else {
699
                $collection->add($embeddedDocumentObject);
700
            }
701 97
        }
702
    }
703 60
704
    private function loadReferenceManyCollectionOwningSide(PersistentCollectionInterface $collection) : void
705 60
    {
706 60
        $hints      = $collection->getHints();
707 60
        $mapping    = $collection->getMapping();
708 60
        $owner      = $collection->getOwner();
709
        $groupedIds = [];
710 60
711
        if ($owner === null) {
712
            throw PersistentCollectionException::ownerRequiredToLoadCollection();
713
        }
714 60
715
        $sorted = isset($mapping['sort']) && $mapping['sort'];
716 60
717 54
        foreach ($collection->getMongoData() as $key => $reference) {
718
            $className = $this->uow->getClassNameForAssociation($mapping, $reference);
719 54
720 1
            if ($mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID && ! is_array($reference)) {
721
                throw HydratorException::associationItemTypeMismatch(get_class($owner), $mapping['name'], $key, 'array', gettype($reference));
722
            }
723 53
724 53
            $identifier = ClassMetadata::getReferenceId($reference, $mapping['storeAs']);
725
            $id         = $this->dm->getClassMetadata($className)->getPHPIdentifierValue($identifier);
726
727 53
            // create a reference to the class and id
728
            $reference = $this->dm->getReference($className, $id);
729
730 53
            // no custom sort so add the references right now in the order they are embedded
731 52
            if (! $sorted) {
732 2
                if (CollectionHelper::isHash($mapping['strategy'])) {
733
                    $collection->set($key, $reference);
734 50
                } else {
735
                    $collection->add($reference);
736
                }
737
            }
738
739 53
            // only query for the referenced object if it is not already initialized or the collection is sorted
740 22
            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...
741
                continue;
742
            }
743 38
744
            $groupedIds[$className][] = $identifier;
745 59
        }
746 38
        foreach ($groupedIds as $className => $ids) {
747 38
            $class           = $this->dm->getClassMetadata($className);
748 38
            $mongoCollection = $this->dm->getDocumentCollection($className);
749 38
            $criteria        = $this->cm->merge(
750 38
                ['_id' => ['$in' => array_values($ids)]],
751 38
                $this->dm->getFilterCollection()->getFilterCriteria($class),
752
                $mapping['criteria'] ?? []
753 38
            );
754
            $criteria        = $this->uow->getDocumentPersister($className)->prepareQueryOrNewObj($criteria);
755 38
756 38
            $options = [];
757 38
            if (isset($mapping['sort'])) {
758
                $options['sort'] = $this->prepareSort($mapping['sort']);
759 38
            }
760
            if (isset($mapping['limit'])) {
761
                $options['limit'] = $mapping['limit'];
762 38
            }
763
            if (isset($mapping['skip'])) {
764
                $options['skip'] = $mapping['skip'];
765 38
            }
766
            if (! empty($hints[Query::HINT_READ_PREFERENCE])) {
767
                $options['readPreference'] = $hints[Query::HINT_READ_PREFERENCE];
768
            }
769 38
770 38
            $cursor    = $mongoCollection->find($criteria, $options);
771 38
            $documents = $cursor->toArray();
772 37
            foreach ($documents as $documentData) {
773 37
                $document = $this->uow->getById($documentData['_id'], $class);
774 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...
775 37
                    $data = $this->hydratorFactory->hydrate($document, $documentData);
776
                    $this->uow->setOriginalDocumentData($document, $data);
777
                }
778 37
779 36
                if (! $sorted) {
780
                    continue;
781
                }
782 1
783
                $collection->add($document);
784
            }
785 59
        }
786
    }
787 17
788
    private function loadReferenceManyCollectionInverseSide(PersistentCollectionInterface $collection) : void
789 17
    {
790 17
        $query    = $this->createReferenceManyInverseSideQuery($collection);
791 17
        $iterator = $query->execute();
792 17
        assert($iterator instanceof Iterator);
793 17
        $documents = $iterator->toArray();
794 16
        foreach ($documents as $key => $document) {
795
            $collection->add($document);
796 17
        }
797
    }
798 17
799
    public function createReferenceManyInverseSideQuery(PersistentCollectionInterface $collection) : Query
800 17
    {
801 17
        $hints   = $collection->getHints();
802 17
        $mapping = $collection->getMapping();
803
        $owner   = $collection->getOwner();
804 17
805
        if ($owner === null) {
806
            throw PersistentCollectionException::ownerRequiredToLoadCollection();
807
        }
808 17
809 17
        $ownerClass        = $this->dm->getClassMetadata(get_class($owner));
810 17
        $targetClass       = $this->dm->getClassMetadata($mapping['targetDocument']);
811 17
        $mappedByMapping   = $targetClass->fieldMappings[$mapping['mappedBy']] ?? [];
812
        $mappedByFieldName = ClassMetadata::getReferenceFieldName($mappedByMapping['storeAs'] ?? ClassMetadata::REFERENCE_STORE_AS_DB_REF, $mapping['mappedBy']);
813 17
814 17
        $criteria = $this->cm->merge(
815 17
            [$mappedByFieldName => $ownerClass->getIdentifierObject($owner)],
816 17
            $this->dm->getFilterCollection()->getFilterCriteria($targetClass),
817
            $mapping['criteria'] ?? []
818 17
        );
819 17
        $criteria = $this->uow->getDocumentPersister($mapping['targetDocument'])->prepareQueryOrNewObj($criteria);
820 17
        $qb       = $this->dm->createQueryBuilder($mapping['targetDocument'])
821
            ->setQueryArray($criteria);
822 17
823 17
        if (isset($mapping['sort'])) {
824
            $qb->sort($mapping['sort']);
825 17
        }
826 2
        if (isset($mapping['limit'])) {
827
            $qb->limit($mapping['limit']);
828 17
        }
829
        if (isset($mapping['skip'])) {
830
            $qb->skip($mapping['skip']);
831
        }
832 17
833
        if (! empty($hints[Query::HINT_READ_PREFERENCE])) {
834
            $qb->setReadPreference($hints[Query::HINT_READ_PREFERENCE]);
835
        }
836 17
837 4
        foreach ($mapping['prime'] as $field) {
838
            $qb->field($field)->prime(true);
839
        }
840 17
841
        return $qb->getQuery();
842
    }
843 5
844
    private function loadReferenceManyWithRepositoryMethod(PersistentCollectionInterface $collection) : void
845 5
    {
846 5
        $cursor    = $this->createReferenceManyWithRepositoryMethodCursor($collection);
847 5
        $mapping   = $collection->getMapping();
848 5
        $documents = $cursor->toArray();
849 5
        foreach ($documents as $key => $obj) {
850 1
            if (CollectionHelper::isHash($mapping['strategy'])) {
851
                $collection->set($key, $obj);
852 4
            } else {
853
                $collection->add($obj);
854
            }
855 5
        }
856
    }
857 5
858
    public function createReferenceManyWithRepositoryMethodCursor(PersistentCollectionInterface $collection) : Iterator
859 5
    {
860 5
        $mapping          = $collection->getMapping();
861 5
        $repositoryMethod = $mapping['repositoryMethod'];
862 5
        $cursor           = $this->dm->getRepository($mapping['targetDocument'])
863
            ->$repositoryMethod($collection->getOwner());
864 5
865
        if (! $cursor instanceof Iterator) {
866
            throw new BadMethodCallException(sprintf('Expected repository method %s to return an iterable object', $repositoryMethod));
867
        }
868 5
869 1
        if (! empty($mapping['prime'])) {
870 1
            $referencePrimer = new ReferencePrimer($this->dm, $this->dm->getUnitOfWork());
871 1
            $primers         = array_combine($mapping['prime'], array_fill(0, count($mapping['prime']), true));
872
            $class           = $this->dm->getClassMetadata($mapping['targetDocument']);
873 1
874
            assert(is_array($primers));
875 1
876
            $cursor = new PrimingIterator($cursor, $class, $referencePrimer, $primers, $collection->getHints());
877
        }
878 5
879
        return $cursor;
880
    }
881
882
    /**
883
     * Prepare a projection array by converting keys, which are PHP property
884
     * names, to MongoDB field names.
885 15
     */
886
    public function prepareProjection(array $fields) : array
887 15
    {
888
        $preparedFields = [];
889 15
890 15
        foreach ($fields as $key => $value) {
891
            $preparedFields[$this->prepareFieldName($key)] = $value;
892
        }
893 15
894
        return $preparedFields;
895
    }
896
897
    /**
898
     * @param int|string $sort
899
     *
900
     * @return int|string|null
901 26
     */
902
    private function getSortDirection($sort)
903 26
    {
904 26
        switch (strtolower((string) $sort)) {
905 15
            case 'desc':
906 23
                return -1;
907 13
            case 'asc':
908
                return 1;
909
        }
910 13
911
        return $sort;
912
    }
913
914
    /**
915
     * Prepare a sort specification array by converting keys to MongoDB field
916
     * names and changing direction strings to int.
917 142
     */
918
    public function prepareSort(array $fields) : array
919 142
    {
920
        $sortFields = [];
921 142
922 26
        foreach ($fields as $key => $value) {
923 1
            if (is_array($value)) {
924
                $sortFields[$this->prepareFieldName($key)] = $value;
925 26
            } else {
926
                $sortFields[$this->prepareFieldName($key)] = $this->getSortDirection($value);
927
            }
928
        }
929 142
930
        return $sortFields;
931
    }
932
933
    /**
934
     * Prepare a mongodb field name and convert the PHP property names to
935
     * MongoDB field names.
936 462
     */
937
    public function prepareFieldName(string $fieldName) : string
938 462
    {
939
        $fieldNames = $this->prepareQueryElement($fieldName, null, null, false);
940 462
941
        return $fieldNames[0][0];
942
    }
943
944
    /**
945
     * Adds discriminator criteria to an already-prepared query.
946
     *
947
     * If the class we're querying has a discriminator field set, we add all
948
     * possible discriminator values to the query. The list of possible
949
     * discriminator values is based on the discriminatorValue of the class
950
     * itself as well as those of all its subclasses.
951
     *
952
     * This method should be used once for query criteria and not be used for
953
     * nested expressions. It should be called before
954
     * {@link DocumentPerister::addFilterToPreparedQuery()}.
955 524
     */
956
    public function addDiscriminatorToPreparedQuery(array $preparedQuery) : array
957 524
    {
958 501
        if (isset($preparedQuery[$this->class->discriminatorField]) || $this->class->discriminatorField === null) {
959
            return $preparedQuery;
960
        }
961 32
962
        $discriminatorValues = $this->getClassDiscriminatorValues($this->class);
963 32
964 1
        if ($discriminatorValues === []) {
965
            return $preparedQuery;
966
        }
967 32
968 21
        if (count($discriminatorValues) === 1) {
969
            $preparedQuery[$this->class->discriminatorField] = $discriminatorValues[0];
970 14
        } else {
971
            $preparedQuery[$this->class->discriminatorField] = ['$in' => $discriminatorValues];
972
        }
973 32
974
        return $preparedQuery;
975
    }
976
977
    /**
978
     * Adds filter criteria to an already-prepared query.
979
     *
980
     * This method should be used once for query criteria and not be used for
981
     * nested expressions. It should be called after
982
     * {@link DocumentPerister::addDiscriminatorToPreparedQuery()}.
983 525
     */
984
    public function addFilterToPreparedQuery(array $preparedQuery) : array
985
    {
986
        /* If filter criteria exists for this class, prepare it and merge
987
         * over the existing query.
988
         *
989
         * @todo Consider recursive merging in case the filter criteria and
990
         * prepared query both contain top-level $and/$or operators.
991 525
         */
992 525
        $filterCriteria = $this->dm->getFilterCollection()->getFilterCriteria($this->class);
993 18
        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...
994
            $preparedQuery = $this->cm->merge($preparedQuery, $this->prepareQueryOrNewObj($filterCriteria));
995
        }
996 525
997
        return $preparedQuery;
998
    }
999
1000
    /**
1001
     * Prepares the query criteria or new document object.
1002
     *
1003
     * PHP field names and types will be converted to those used by MongoDB.
1004 600
     */
1005
    public function prepareQueryOrNewObj(array $query, bool $isNewObj = false) : array
1006 600
    {
1007
        $preparedQuery = [];
1008 600
1009
        foreach ($query as $key => $value) {
1010 556
            // Recursively prepare logical query clauses
1011 20
            if (in_array($key, ['$and', '$or', '$nor'], true) && is_array($value)) {
1012 20
                foreach ($value as $k2 => $v2) {
1013
                    $preparedQuery[$key][$k2] = $this->prepareQueryOrNewObj($v2, $isNewObj);
1014 20
                }
1015
                continue;
1016
            }
1017 556
1018 74
            if (isset($key[0]) && $key[0] === '$' && is_array($value)) {
1019 74
                $preparedQuery[$key] = $this->prepareQueryOrNewObj($value, $isNewObj);
1020
                continue;
1021
            }
1022 556
1023 556
            $preparedQueryElements = $this->prepareQueryElement((string) $key, $value, null, true, $isNewObj);
1024 556
            foreach ($preparedQueryElements as [$preparedKey, $preparedValue]) {
1025 153
                $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...
1026 505
                    ? array_map('\Doctrine\ODM\MongoDB\Types\Type::convertPHPToDatabaseValue', $preparedValue)
1027
                    : Type::convertPHPToDatabaseValue($preparedValue);
1028
            }
1029
        }
1030 600
1031
        return $preparedQuery;
1032
    }
1033
1034
    /**
1035
     * Prepares a query value and converts the PHP value to the database value
1036
     * if it is an identifier.
1037
     *
1038
     * It also handles converting $fieldName to the database name if they are
1039
     * different.
1040
     *
1041
     * @param mixed $value
1042 978
     */
1043
    private function prepareQueryElement(string $fieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false) : array
1044 978
    {
1045
        $class = $class ?? $this->class;
1046
1047
        // @todo Consider inlining calls to ClassMetadata methods
1048
1049 978
        // Process all non-identifier fields by translating field names
1050 281
        if ($class->hasField($fieldName) && ! $class->isIdentifier($fieldName)) {
1051 281
            $mapping   = $class->fieldMappings[$fieldName];
1052
            $fieldName = $mapping['name'];
1053 281
1054 77
            if (! $prepareValue) {
1055
                return [[$fieldName, $value]];
1056
            }
1057
1058 214
            // Prepare mapped, embedded objects
1059 214
            if (! empty($mapping['embedded']) && is_object($value) &&
1060 3
                ! $this->dm->getMetadataFactory()->isTransient(get_class($value))) {
1061
                return [[$fieldName, $this->pb->prepareEmbeddedDocumentValue($mapping, $value)]];
1062
            }
1063 212
1064
            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...
1065 14
                try {
1066 1
                    return $this->prepareReference($fieldName, $value, $mapping, $inNewObj);
1067
                } 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...
1068
                    // do nothing in case passed object is not mapped document
1069
                }
1070
            }
1071
1072 199
            // No further preparation unless we're dealing with a simple reference
1073 132
            if (empty($mapping['reference']) || $mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID || empty((array) $value)) {
1074
                return [[$fieldName, $value]];
1075
            }
1076
1077 94
            // Additional preparation for one or more simple reference values
1078
            $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1079 94
1080 90
            if (! is_array($value)) {
1081
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1082
            }
1083
1084 6
            // Objects without operators or with DBRef fields can be converted immediately
1085 3
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1086
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1087
            }
1088 6
1089
            return [[$fieldName, $this->prepareQueryExpression($value, $targetClass)]];
1090
        }
1091
1092 868
        // Process identifier fields
1093 358
        if (($class->hasField($fieldName) && $class->isIdentifier($fieldName)) || $fieldName === '_id') {
1094
            $fieldName = '_id';
1095 358
1096 42
            if (! $prepareValue) {
1097
                return [[$fieldName, $value]];
1098
            }
1099 319
1100 293
            if (! is_array($value)) {
1101
                return [[$fieldName, $class->getDatabaseIdentifierValue($value)]];
1102
            }
1103
1104 60
            // Objects without operators or with DBRef fields can be converted immediately
1105 6
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1106
                return [[$fieldName, $class->getDatabaseIdentifierValue($value)]];
1107
            }
1108 55
1109
            return [[$fieldName, $this->prepareQueryExpression($value, $class)]];
1110
        }
1111
1112 611
        // No processing for unmapped, non-identifier, non-dotted field names
1113 463
        if (strpos($fieldName, '.') === false) {
1114
            return [[$fieldName, $value]];
1115
        }
1116
1117
        /* Process "fieldName.objectProperty" queries (on arrays or objects).
1118
         *
1119
         * We can limit parsing here, since at most three segments are
1120
         * significant: "fieldName.objectProperty" with an optional index or key
1121
         * for collections stored as either BSON arrays or objects.
1122 160
         */
1123
        $e = explode('.', $fieldName, 4);
1124
1125 160
        // No further processing for unmapped fields
1126 6
        if (! isset($class->fieldMappings[$e[0]])) {
1127
            return [[$fieldName, $value]];
1128
        }
1129 155
1130 155
        $mapping = $class->fieldMappings[$e[0]];
1131
        $e[0]    = $mapping['name'];
1132
1133 155
        // Hash and raw fields will not be prepared beyond the field name
1134 1
        if ($mapping['type'] === Type::HASH || $mapping['type'] === Type::RAW) {
1135
            $fieldName = implode('.', $e);
1136 1
1137
            return [[$fieldName, $value]];
1138
        }
1139 154
1140 154
        if ($mapping['type'] === 'many' && CollectionHelper::isHash($mapping['strategy'])
1141 1
                && isset($e[2])) {
1142 1
            $objectProperty       = $e[2];
1143 1
            $objectPropertyPrefix = $e[1] . '.';
1144 153
            $nextObjectProperty   = implode('.', array_slice($e, 3));
1145 152
        } elseif ($e[1] !== '$') {
1146 152
            $fieldName            = $e[0] . '.' . $e[1];
1147 152
            $objectProperty       = $e[1];
1148 152
            $objectPropertyPrefix = '';
1149 1
            $nextObjectProperty   = implode('.', array_slice($e, 2));
1150 1
        } elseif (isset($e[2])) {
1151 1
            $fieldName            = $e[0] . '.' . $e[1] . '.' . $e[2];
1152 1
            $objectProperty       = $e[2];
1153 1
            $objectPropertyPrefix = $e[1] . '.';
1154
            $nextObjectProperty   = implode('.', array_slice($e, 3));
1155 1
        } else {
1156
            $fieldName = $e[0] . '.' . $e[1];
1157 1
1158
            return [[$fieldName, $value]];
1159
        }
1160
1161 154
        // No further processing for fields without a targetDocument mapping
1162 5
        if (! isset($mapping['targetDocument'])) {
1163
            if ($nextObjectProperty) {
1164
                $fieldName .= '.' . $nextObjectProperty;
1165
            }
1166 5
1167
            return [[$fieldName, $value]];
1168
        }
1169 149
1170
        $targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1171
1172 149
        // No further processing for unmapped targetDocument fields
1173 26
        if (! $targetClass->hasField($objectProperty)) {
1174
            if ($nextObjectProperty) {
1175
                $fieldName .= '.' . $nextObjectProperty;
1176
            }
1177 26
1178
            return [[$fieldName, $value]];
1179
        }
1180 128
1181 128
        $targetMapping      = $targetClass->getFieldMapping($objectProperty);
1182
        $objectPropertyIsId = $targetClass->isIdentifier($objectProperty);
1183
1184 128
        // Prepare DBRef identifiers or the mapped field's property path
1185 108
        $fieldName = $objectPropertyIsId && ! empty($mapping['reference']) && $mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID
1186 128
            ? ClassMetadata::getReferenceFieldName($mapping['storeAs'], $e[0])
1187
            : $e[0] . '.' . $objectPropertyPrefix . $targetMapping['name'];
1188
1189 128
        // Process targetDocument identifier fields
1190 109
        if ($objectPropertyIsId) {
1191 7
            if (! $prepareValue) {
1192
                return [[$fieldName, $value]];
1193
            }
1194 102
1195 88
            if (! is_array($value)) {
1196
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1197
            }
1198
1199 16
            // Objects without operators or with DBRef fields can be converted immediately
1200 6
            if (! $this->hasQueryOperators($value) || $this->hasDBRefFields($value)) {
1201
                return [[$fieldName, $targetClass->getDatabaseIdentifierValue($value)]];
1202
            }
1203 16
1204
            return [[$fieldName, $this->prepareQueryExpression($value, $targetClass)]];
1205
        }
1206
1207
        /* The property path may include a third field segment, excluding the
1208
         * collection item pointer. If present, this next object property must
1209
         * be processed recursively.
1210 19
         */
1211
        if ($nextObjectProperty) {
1212 16
            // Respect the targetDocument's class metadata when recursing
1213 10
            $nextTargetClass = isset($targetMapping['targetDocument'])
1214 16
                ? $this->dm->getClassMetadata($targetMapping['targetDocument'])
1215
                : null;
1216 16
1217 14
            if (empty($targetMapping['reference'])) {
1218
                $fieldNames = $this->prepareQueryElement($nextObjectProperty, $value, $nextTargetClass, $prepareValue);
1219
            } else {
1220 4
                // No recursive processing for references as most probably somebody is querying DBRef or alike
1221 1
                if ($nextObjectProperty[0] !== '$' && in_array($targetMapping['storeAs'], [ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB, ClassMetadata::REFERENCE_STORE_AS_DB_REF])) {
1222
                    $nextObjectProperty = '$' . $nextObjectProperty;
1223 4
                }
1224
                $fieldNames = [[$nextObjectProperty, $value]];
1225
            }
1226
1227 16
            return array_map(static function ($preparedTuple) use ($fieldName) {
1228
                [$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...
1229 16
1230 16
                return [$fieldName . '.' . $key, $value];
1231
            }, $fieldNames);
1232
        }
1233 5
1234
        return [[$fieldName, $value]];
1235
    }
1236 77
1237
    private function prepareQueryExpression(array $expression, ClassMetadata $class) : array
1238 77
    {
1239
        foreach ($expression as $k => $v) {
1240 77
            // Ignore query operators whose arguments need no type conversion
1241 16
            if (in_array($k, ['$exists', '$type', '$mod', '$size'])) {
1242
                continue;
1243
            }
1244
1245 77
            // Process query operators whose argument arrays need type conversion
1246 75
            if (in_array($k, ['$in', '$nin', '$all']) && is_array($v)) {
1247 75
                foreach ($v as $k2 => $v2) {
1248
                    $expression[$k][$k2] = $class->getDatabaseIdentifierValue($v2);
1249 75
                }
1250
                continue;
1251
            }
1252
1253 18
            // Recursively process expressions within a $not operator
1254 15
            if ($k === '$not' && is_array($v)) {
1255 15
                $expression[$k] = $this->prepareQueryExpression($v, $class);
1256
                continue;
1257
            }
1258 18
1259
            $expression[$k] = $class->getDatabaseIdentifierValue($v);
1260
        }
1261 77
1262
        return $expression;
1263
    }
1264
1265
    /**
1266
     * Checks whether the value has DBRef fields.
1267
     *
1268
     * This method doesn't check if the the value is a complete DBRef object,
1269
     * although it should return true for a DBRef. Rather, we're checking that
1270
     * the value has one or more fields for a DBref. In practice, this could be
1271
     * $elemMatch criteria for matching a DBRef.
1272
     *
1273
     * @param mixed $value
1274 78
     */
1275
    private function hasDBRefFields($value) : bool
1276 78
    {
1277
        if (! is_array($value) && ! is_object($value)) {
1278
            return false;
1279
        }
1280 78
1281
        if (is_object($value)) {
1282
            $value = get_object_vars($value);
1283
        }
1284 78
1285 78
        foreach ($value as $key => $_) {
1286 4
            if ($key === '$ref' || $key === '$id' || $key === '$db') {
1287
                return true;
1288
            }
1289
        }
1290 77
1291
        return false;
1292
    }
1293
1294
    /**
1295
     * Checks whether the value has query operators.
1296
     *
1297
     * @param mixed $value
1298 82
     */
1299
    private function hasQueryOperators($value) : bool
1300 82
    {
1301
        if (! is_array($value) && ! is_object($value)) {
1302
            return false;
1303
        }
1304 82
1305
        if (is_object($value)) {
1306
            $value = get_object_vars($value);
1307
        }
1308 82
1309 82
        foreach ($value as $key => $_) {
1310 78
            if (isset($key[0]) && $key[0] === '$') {
1311
                return true;
1312
            }
1313
        }
1314 11
1315
        return false;
1316
    }
1317
1318
    /**
1319
     * Returns the list of discriminator values for the given ClassMetadata
1320 32
     */
1321
    private function getClassDiscriminatorValues(ClassMetadata $metadata) : array
1322 32
    {
1323
        $discriminatorValues = [];
1324 32
1325 29
        if ($metadata->discriminatorValue !== null) {
1326
            $discriminatorValues[] = $metadata->discriminatorValue;
1327
        }
1328 32
1329 12
        foreach ($metadata->subClasses as $className) {
1330 12
            $key = array_search($className, $metadata->discriminatorMap);
1331
            if (! $key) {
1332
                continue;
1333
            }
1334 12
1335
            $discriminatorValues[] = $key;
1336
        }
1337
1338 32
        // If a defaultDiscriminatorValue is set and it is among the discriminators being queries, add NULL to the list
1339 3
        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...
1340
            $discriminatorValues[] = null;
1341
        }
1342 32
1343
        return $discriminatorValues;
1344
    }
1345 595
1346
    private function handleCollections(object $document, array $options) : void
1347
    {
1348 595
        // Collection deletions (deletions of complete collections)
1349 595
        $collections = [];
1350 113
        foreach ($this->uow->getScheduledCollections($document) as $coll) {
1351 102
            if (! $this->uow->isCollectionScheduledForDeletion($coll)) {
1352
                continue;
1353
            }
1354 33
1355
            $collections[] = $coll;
1356 595
        }
1357 33
        if (! empty($collections)) {
1358
            $this->cp->delete($document, $collections, $options);
1359
        }
1360 595
        // Collection updates (deleteRows, updateRows, insertRows)
1361 595
        $collections = [];
1362 113
        foreach ($this->uow->getScheduledCollections($document) as $coll) {
1363 29
            if (! $this->uow->isCollectionScheduledForUpdate($coll)) {
1364
                continue;
1365
            }
1366 105
1367
            $collections[] = $coll;
1368 595
        }
1369 105
        if (! empty($collections)) {
1370
            $this->cp->update($document, $collections, $options);
1371
        }
1372 595
        // Take new snapshots from visited collections
1373 252
        foreach ($this->uow->getVisitedCollections($document) as $coll) {
1374
            $coll->takeSnapshot();
1375 595
        }
1376
    }
1377
1378
    /**
1379
     * If the document is new, ignore shard key field value, otherwise throw an
1380
     * exception. Also, shard key field should be present in actual document
1381
     * data.
1382
     *
1383
     * @throws MongoDBException
1384 10
     */
1385
    private function guardMissingShardKey(object $document, string $shardKeyField, array $actualDocumentData) : void
1386 10
    {
1387 10
        $dcs      = $this->uow->getDocumentChangeSet($document);
1388
        $isUpdate = $this->uow->isScheduledForUpdate($document);
1389 10
1390 10
        $fieldMapping = $this->class->getFieldMappingByDbFieldName($shardKeyField);
1391
        $fieldName    = $fieldMapping['fieldName'];
1392 10
1393 2
        if ($isUpdate && isset($dcs[$fieldName]) && $dcs[$fieldName][0] !== $dcs[$fieldName][1]) {
1394
            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...
1395
        }
1396 8
1397
        if (! isset($actualDocumentData[$fieldName])) {
1398
            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...
1399 8
        }
1400
    }
1401
1402
    /**
1403
     * Get shard key aware query for single document.
1404 310
     */
1405
    private function getQueryForDocument(object $document) : array
1406 310
    {
1407 310
        $id = $this->uow->getDocumentIdentifier($document);
1408
        $id = $this->class->getDatabaseIdentifierValue($id);
1409 310
1410
        $shardKeyQueryPart = $this->getShardKeyQuery($document);
1411 308
1412
        return array_merge(['_id' => $id], $shardKeyQueryPart);
1413
    }
1414 606
1415
    private function getWriteOptions(array $options = []) : array
1416 606
    {
1417 606
        $defaultOptions  = $this->dm->getConfiguration()->getDefaultCommitOptions();
1418 606
        $documentOptions = [];
1419 9
        if ($this->class->hasWriteConcern()) {
1420
            $documentOptions['w'] = $this->class->getWriteConcern();
1421
        }
1422 606
1423
        return array_merge($defaultOptions, $documentOptions, $options);
1424
    }
1425 15
1426
    private function prepareReference(string $fieldName, $value, array $mapping, bool $inNewObj) : array
1427 15
    {
1428 14
        $reference = $this->dm->createReference($value, $mapping);
1429 8
        if ($inNewObj || $mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_ID) {
1430
            return [[$fieldName, $reference]];
1431
        }
1432 6
1433
        switch ($mapping['storeAs']) {
1434
            case ClassMetadata::REFERENCE_STORE_AS_REF:
1435
                $keys = ['id' => true];
1436
                break;
1437
1438
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
1439 6
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
1440
                $keys = ['$ref' => true, '$id' => true, '$db' => true];
1441 6
1442 5
                if ($mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_DB_REF) {
1443
                    unset($keys['$db']);
1444
                }
1445 6
1446 4
                if (isset($mapping['targetDocument'])) {
1447
                    unset($keys['$ref'], $keys['$db']);
1448 6
                }
1449
                break;
1450
1451
            default:
1452
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $mapping['storeAs']));
1453
        }
1454 6
1455 2
        if ($mapping['type'] === 'many') {
1456
            return [[$fieldName, ['$elemMatch' => array_intersect_key($reference, $keys)]]];
1457
        }
1458 4
1459
        return array_map(
1460 4
            static function ($key) use ($reference, $fieldName) {
1461 4
                return [$fieldName . '.' . $key, $reference[$key]];
1462 4
            },
1463
            array_keys($keys)
1464
        );
1465
    }
1466
}
1467