Issues (126)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Db/AbstractMongoDao.php (21 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
declare(strict_types=1);
3
/**
4
 * Minotaur
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
 * use this file except in compliance with the License. You may obtain a copy of
8
 * the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 * License for the specific language governing permissions and limitations under
16
 * the License.
17
 *
18
 * @copyright 2015-2017 Appertly
19
 * @license   Apache-2.0
20
 */
21
namespace Minotaur\Db;
22
23
use ArrayIterator;
24
use MongoDB\BSON\ObjectID;
25
use MongoDB\Driver\Cursor;
26
use MongoDB\Driver\Manager;
27
use MongoDB\Driver\ReadPreference;
28
use MongoDB\Driver\WriteConcern;
29
use MongoDB\Driver\WriteResult;
30
use Caridea\Dao\MongoDb as MongoDbDao;
31
use Caridea\Event\PublisherAware;
32
use Minotaur\Getter;
33
34
/**
35
 * Abstract MongoDB DAO Service
36
 */
37
abstract class AbstractMongoDao extends MongoDbDao implements EntityRepo, DbRefResolver, PublisherAware
38
{
39
    use MongoHelper;
40
    use \Caridea\Dao\Event\Publishing;
41
42
    /**
43
     * @var bool Whether to enforce optimistic locking
44
     */
45
    private $versioned = true;
46
    /**
47
     * @var bool Whether entities will be put in the first-level cache
48
     */
49
    private $caching = true;
50
    /**
51
     * @var array<string,?string> The MongoDB type map when reading records
52
     */
53
    private $typeMap = ['root' => null, 'document' => null];
54
    /**
55
     * @var \MongoDB\Driver\ReadPreference The MongoDB read preference, or `null`
56
     */
57
    private $readPreference;
58
    /**
59
     * @var \MongoDB\Driver\ReadPreference The MongoDB write concern, or `null`
60
     */
61
    private $writeConcern;
62
    /**
63
     * @var array<string,mixed> First-level cache
64
     */
65
    private $cache = [];
66
67
    /**
68
     * Creates a new AbstractMongoDao.
69
     *
70
     * Current accepted configuration values:
71
     * * `versioned` – Whether to enforce optimistic locking via a version field (default: true)
72
     * * `caching` – Whether to cache entities by ID (default: true)
73
     * * `typeMapRoot` – The type used to unserialize BSON root documents
74
     * * `typeMapDocument` – The type used to unserialize BSON nested documents
75
     * * `readPreference` – Must be a `MongoDB\Driver\ReadPreference`
76
     * * `writeConcern` – Must be a `MongoDB\Driver\WriteConcern`
77
     *
78
     * As for the `typeMap` options, you can see
79
     * [Deserialization from BSON](http://php.net/manual/en/mongodb.persistence.deserialization.php#mongodb.persistence.typemaps)
80
     * for more information.
81
     *
82
     * @param \MongoDB\Driver\Manager $manager The MongoDB Manager
83
     * @param string $collection The collection to wrap
84
     * @param array<string,mixed> $options Optional. Map of configuration values
85
     */
86
    public function __construct(
87
        Manager $manager,
88
        string $collection,
89
        array $options = null
90
    ) {
91
        parent::__construct($manager, $collection);
92
        if ($options !== null) {
93
            $this->versioned = array_key_exists('version', $options) ?
94
                (bool) $options['version'] : true;
95
            $this->caching = array_key_exists('caching', $options) ?
96
                (bool) $options['caching'] : true;
97
            if (array_key_exists('typeMapRoot', $options)) {
98
                $r = $options['typeMapRoot'];
99
                $this->typeMap['root'] = $r === null ? null : (string)$r;
100
            }
101
            if (array_key_exists('typeMapDocument', $options)) {
102
                $d = $options['typeMapDocument'];
103
                $this->typeMap['document'] = $d === null ? null : (string)$d;
104
            }
105
            $rp = $options['readPreference'] ?? null;
106
            if ($rp instanceof ReadPreference) {
0 ignored issues
show
The class MongoDB\Driver\ReadPreference 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...
107
                $this->readPreference = $rp;
108
            }
109
            $wc = $options['writeConcern'] ?? null;
110
            if ($wc instanceof WriteConcern) {
0 ignored issues
show
The class MongoDB\Driver\WriteConcern 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...
111
                $this->writeConcern = $wc;
0 ignored issues
show
Documentation Bug introduced by
It seems like $wc of type object<MongoDB\Driver\WriteConcern> is incompatible with the declared type object<MongoDB\Driver\ReadPreference> of property $writeConcern.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
112
            }
113
        }
114
        $this->publisher = new \Caridea\Event\NullPublisher();
115
    }
116
117
    /**
118
     * @inheritDoc
119
     */
120
    public function countAll(array $criteria): int
121
    {
122
        $result = $this->doExecute(function (Manager $m, string $c) use ($criteria) {
123
            list($db, $coll) = explode('.', $c, 2);
124
            $command = new \MongoDB\Driver\Command([
125
                'count' => $coll,
126
                'query' => $criteria,
127
            ]);
128
            $cursor = $m->executeCommand($db, $command, $this->readPreference);
129
            $cursor->setTypeMap(['root' => 'array']);
130
            $resa = $cursor->toArray();
131
            return count($resa) > 0 ? current($resa) : null;
132
        });
133
134
        // Older server versions may return a float
135
        if (!is_array($result) || !array_key_exists('n', $result) || !(is_int($result['n']) || is_float($result['n']))) {
136
            throw new \Caridea\Dao\Exception\Unretrievable('count command did not return a numeric "n" value');
137
        }
138
139
        return (int) $result['n'];
140
    }
141
142
    /**
143
     * @inheritDoc
144
     */
145
    public function findOne(array $criteria)
146
    {
147
        return $this->maybeCache(
148
            $this->doExecute(function (Manager $m, string $c) use ($criteria) {
149
                $q = new \MongoDB\Driver\Query($criteria, ['limit' => 1]);
150
                $res = $m->executeQuery($c, $q, $this->readPreference);
151
                $res->setTypeMap($this->typeMap);
152
                $resa = $res->toArray();
153
                return count($resa) > 0 ? current($resa) : null;
154
            })
155
        );
156
    }
157
158
    /**
159
     * @inheritDoc
160
     */
161
    public function findAll(array $criteria, \Caridea\Http\Pagination $pagination = null, bool $totalCount = false): iterable
162
    {
163
        $total = null;
164
        if ($totalCount === true && $pagination !== null && ($pagination->getMax() != PHP_INT_MAX || $pagination->getOffset() > 0)) {
165
            $total = $this->countAll($criteria);
166
        }
167
        $results = $this->doExecute(function (Manager $m, string $c) use ($criteria, $pagination) {
168
            $qo = [];
169
            if ($pagination !== null) {
170
                if ($pagination->getMax() != PHP_INT_MAX) {
171
                    $qo['limit'] = $pagination->getMax();
172
                }
173
                $qo['skip'] = $pagination->getOffset();
174
                $sorts = [];
175
                foreach ($pagination->getOrder() as $k => $v) {
176
                    $sorts[$k] = $v ? 1 : -1;
177
                }
178
                if (count($sorts) > 0) {
179
                    $qo['sort'] = $sorts;
180
                }
181
            }
182
            $q = new \MongoDB\Driver\Query($criteria, $qo);
183
            $res = $m->executeQuery($c, $q, $this->readPreference);
184
            $res->setTypeMap($this->typeMap);
185
            return $res;
186
        });
187
        return $total === null ? $results : new CursorSubset($results, $total);
188
    }
189
190
    /**
191
     * @inheritDoc
192
     */
193
    public function findById($id)
194
    {
195
        try {
196
            $mid = $this->toId($id);
197
        } catch (\MongoDB\Driver\Exception\InvalidArgumentException $e) {
0 ignored issues
show
The class MongoDB\Driver\Exception\InvalidArgumentException 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...
198
            if ($e->getMessage() === 'Invalid BSON ID provided') {
199
                throw new \Caridea\Dao\Exception\Unretrievable('Could not find document', 0, $e);
200
            }
201
            throw $e;
202
        }
203
        return $this->getFromCache((string)$id) ??
204
            $this->findOne(['_id' => $mid]);
205
    }
206
207
    /**
208
     * @inheritDoc
209
     */
210
    public function get($id)
211
    {
212
        return $this->ensure($id, $this->findById($id));
213
    }
214
215
    /**
216
     * @inheritDoc
217
     */
218
    public function getAll(iterable $ids): iterable
219
    {
220
        $ids = is_array($ids) ? $ids : iterator_to_array($ids);
221
        if (count($ids) === 0) {
222
            return [];
223
        }
224
        try {
225
            $mids = $this->toIds($ids);
226
        } catch (\MongoDB\Driver\Exception\InvalidArgumentException $e) {
0 ignored issues
show
The class MongoDB\Driver\Exception\InvalidArgumentException 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...
227
            if ($e->getMessage() === 'Invalid BSON ID provided') {
228
                throw new \Caridea\Dao\Exception\Unretrievable('Could not load documents', 0, $e);
229
            }
230
            throw $e;
231
        }
232
        $cmp = array_flip(array_map(function ($a) {
233
            return (string) $a;
234
        }, $ids));
235
        $fromCache = array_intersect_key($this->cache, $cmp);
236
        if (count($fromCache) === count($ids)) {
237
            return $fromCache;
238
        } elseif (count($fromCache) > 0) {
239
            $mids = array_filter($mids, function ($a) {
240
                return !array_key_exists((string)$a, $this->cache);
241
            });
242
            if(count($mids) < 2){
0 ignored issues
show
Expected 1 space after IF keyword; 0 found
Loading history...
243
              return array_merge(
244
                  $fromCache,
245
                  $this->maybeCacheAll($this->findAll(['_id' => $mids]))
246
              );
247
            }
248
            return array_merge(
249
                $fromCache,
250
                $this->maybeCacheAll($this->findAll(['_id' => ['$in' => array_values($mids)]]))
251
            );
252
        } else {
253
            return $this->maybeCacheAll($this->findAll(['_id' => ['$in' => array_values($mids)]]));
254
        }
255
    }
256
257
    /**
258
     * @inheritDoc
259
     */
260
    public function getInstanceMap(iterable $entities): array
261
    {
262
        $instances = [];
263
        foreach ($entities as $entity) {
264
            $instances[(string) Getter::getId($entity)] = $entity;
265
        }
266
        return $instances;
267
    }
268
269
    /**
270
     * Gets the read preference.
271
     *
272
     * If no read preference was specified at creation, this method returns the
273
     * read preference as returned by the `Manager`.
274
     *
275
     * @return - The read preference, or `null`
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
276
     */
277
    public function getReadPreference(): ReadPreference
278
    {
279
        return $this->readPreference ?? $this->manager->getReadPreference();
280
    }
281
282
    /**
283
     * Gets the write concern.
284
     *
285
     * If no write concern was specified at creation, this method returns the
286
     * write concern as returned by the `Manager`.
287
     *
288
     * @return - The write concern
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
289
     */
290
    public function getWriteConcern(): WriteConcern
291
    {
292
        return $this->writeConcern ?? $this->manager->getWriteConcern();
293
    }
294
295
    /**
296
     * @inheritDoc
297
     */
298
    public function isResolvable(string $ref): bool
299
    {
300
        return strstr($this->collection, '.') === ".$ref";
301
    }
302
303
    /**
304
     * @inheritDoc
305
     */
306
    public function resolve(array $ref)
307
    {
308
        if (!array_key_exists('$ref', $ref) || !array_key_exists('$id', $ref)) {
309
            throw new \InvalidArgumentException('Not a DBRef. Needs both $ref and $id keys.');
310
        }
311
        if (!$this->isResolvable($ref['$ref'])) {
312
            throw new \InvalidArgumentException("Unsupported reference type: " . $ref['$ref']);
313
        }
314
        return $this->findById($ref['$id']);
315
    }
316
317
    /**
318
     * @inheritDoc
319
     */
320
    public function resolveAll(iterable $refs): iterable
321
    {
322
        $types = [];
323
        $ids = [];
324
        foreach ($refs as $ref) {
325
            $types[$ref['$ref']] = true;
326
            $ids[] = $ref['$id'];
327
        }
328
        foreach ($types as $type => $_) {
329
            if (!$this->isResolvable($type)) {
330
                throw new \InvalidArgumentException("Unsupported reference type: " . $type);
331
            }
332
        }
333
        return $this->getAll($ids);
334
    }
335
336
    /**
337
     * Creates a record.
338
     *
339
     * @param $record - The record to insert, ready to go
340
     * @return - Whatever MongoDB returns
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
341
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
342
     * @throws \Caridea\Dao\Exception\Violating If a constraint is violated
343
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
344
     */
345
    protected function doCreate(array $record): WriteResult
346
    {
347
        if ($this->versioned) {
348
            $record['version'] = 0;
349
        }
350
351
        return $this->doExecute(function (Manager $m, string $c) use ($record) {
352
            $bulk = new \MongoDB\Driver\BulkWrite();
353
            $bulk->insert($record);
354
            return $m->executeBulkWrite($c, $bulk, $this->writeConcern);
355
        });
356
    }
357
358
    /**
359
     * Creates a record using a MongoDB `Persistable`.
360
     *
361
     * @param $record - The document to insert, ready to go
362
     * @return - Whatever MongoDB returns
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
363
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
364
     * @throws \Caridea\Dao\Exception\Violating If a constraint is violated
365
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
366
     */
367
    protected function doPersist(\MongoDB\BSON\Persistable $record): WriteResult
368
    {
369
        $this->preInsert($record);
370
        $wr = $this->doExecute(function (Manager $m, string $c) use ($record) {
371
            $bulk = new \MongoDB\Driver\BulkWrite();
372
            $bulk->insert($record);
373
            return $m->executeBulkWrite($c, $bulk, $this->writeConcern);
374
        });
375
        $this->postInsert($record);
376
        return $wr;
377
    }
378
379
    /**
380
     * Updates a record.
381
     *
382
     * @param $entity - The entity to update
383
     * @param $version - Optional version for optimistic lock checking
384
     * @return - Whatever MongoDB returns
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
385
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
386
     * @throws \Caridea\Dao\Exception\Conflicting If optimistic/pessimistic lock fails
387
     * @throws \Caridea\Dao\Exception\Violating If a constraint is violated
388
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
389
     */
390
    protected function doUpdateModifiable(Entity\Modifiable $entity, int $version = null): ?WriteResult
391
    {
392
        if (!$entity->isDirty()) {
393
            return null;
394
        }
395
        $mid = Getter::getId($entity);
396
        $ops = $entity->getChanges();
397
398
        if ($this->versioned) {
399
            if ($version !== null) {
400
                $orig = $this->findOne(['_id' => $mid]);
401
                if ($version < (int) Getter::get($orig, 'version')) {
402
                    throw new \Caridea\Dao\Exception\Conflicting("Document version conflict");
403
                }
404
            }
405
            $ops['$inc']['version'] = 1;
406
        }
407
408
        $this->preUpdate($entity);
409
        unset($this->cache[(string) $mid]);
410
        $wr = $this->doExecute(function (Manager $m, string $c) use ($mid, $ops) {
411
            $bulk = new \MongoDB\Driver\BulkWrite();
412
            $bulk->update(['_id' => $mid], $ops);
413
            return $m->executeBulkWrite($c, $bulk, $this->writeConcern);
414
        });
415
        $this->postUpdate($entity);
416
        return $wr;
417
    }
418
419
    /**
420
     * Updates a record.
421
     *
422
     * @param \MongoDB\BSON\ObjectID|string $id The document identifier, either a string or `ObjectID`
423
     * @param array<string,array<string,mixed>> $operations The operations to send to MongoDB
0 ignored issues
show
There is no parameter named $operations. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
424
     * @param $version - Optional version for optimistic lock checking
425
     * @return - Whatever MongoDB returns
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
426
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
427
     * @throws \Caridea\Dao\Exception\Unretrievable If the document doesn't exist
428
     * @throws \Caridea\Dao\Exception\Conflicting If optimistic/pessimistic lock fails
429
     * @throws \Caridea\Dao\Exception\Violating If a constraint is violated
430
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
431
     */
432
    protected function doUpdate($id, array $ops, int $version = null): WriteResult
433
    {
434
        // ensure record exists
435
        $mid = $this->toId($id);
436
        $orig = $this->get($id);
437
438
        // check optimistic locking
439
        if ($this->versioned) {
440
            if ($version !== null) {
441
                if ($version < (int) Getter::get($orig, 'version')) {
442
                    throw new \Caridea\Dao\Exception\Conflicting("Document version conflict");
443
                }
444
            }
445
            $ops['$inc']['version'] = 1;
446
        }
447
448
        // do update operation
449
        unset($this->cache[(string)$id]);
450
        return $this->doExecute(function (Manager $m, string $c) use ($mid, $ops) {
451
            $bulk = new \MongoDB\Driver\BulkWrite();
452
            $bulk->update(['_id' => $mid], $ops);
453
            return $m->executeBulkWrite($c, $bulk, $this->writeConcern);
454
        });
455
    }
456
457
    /**
458
     * Deletes a record.
459
     *
460
     * @param \MongoDB\BSON\ObjectID|string $id The document identifier, either a string or `ObjectID`
461
     * @return - Whatever MongoDB returns
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
462
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
463
     * @throws \Caridea\Dao\Exception\Unretrievable If the document doesn't exist
464
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
465
     */
466
    protected function doDelete($id): WriteResult
467
    {
468
        $mid = $this->toId($id);
469
        $entity = $this->get($mid);
470
        unset($this->cache[(string)$id]);
471
        $this->preDelete($entity);
472
        $wr = $this->doExecute(function (Manager $m, string $c) use ($mid) {
473
            $bulk = new \MongoDB\Driver\BulkWrite();
474
            $bulk->delete(['_id' => $mid], ['limit' => 1]);
475
            return $m->executeBulkWrite($c, $bulk, $this->writeConcern);
476
        });
477
        $this->postDelete($entity);
478
        return $wr;
479
    }
480
481
    /**
482
     * Executes an aggregation command.
483
     *
484
     * @param iterable<array<string,mixed>> $pipeline The aggregation pipeline operations
0 ignored issues
show
The doc-type iterable<array<string,mixed>> could not be parsed: Expected "|" or "end of type", but got "<" at position 8. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
485
     * @param array<string,mixed> $options Any aggregation options
486
     * @see https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/
487
     * @since 0.7.2
488
     */
489
    protected function doAggregate(iterable $pipeline, array $options): \MongoDB\Driver\Cursor
490
    {
491
        return $this->doExecute(function (Manager $m, string $c) use ($pipeline, $options) {
492
            list($db, $coll) = explode('.', $c, 2);
493
            $cmd = [
494
                'aggregate' => $coll,
495
                'pipeline' => $options,
496
            ];
497
            foreach ($options as $k => $v) {
498
                $cmd[$k] = $v;
499
            }
500
            $command = new \MongoDB\Driver\Command($cmd);
501
            return $m->executeCommand($db, $command, $this->readPreference);
502
        });
503
    }
504
505
    /**
506
     * Executes a projection.
507
     *
508
     * @param array<string,mixed> $criteria Field to value pairs
509
     * @param array<string,mixed> $projections Field name to projection value
510
     *        (either boolean or projection operator)
511
     * @param $pagination - Optional pagination parameters
512
     * @param $totalCount - Return a `CursorSubset` that includes the total
513
     *        number of records. This is only done if `$pagination` is not using
514
     *        the defaults.
515
     * @return - The projection cursor
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
516
     * @throws \Caridea\Dao\Exception\Unreachable If the connection fails
517
     * @throws \Caridea\Dao\Exception\Unretrievable If the result cannot be returned
518
     * @throws \Caridea\Dao\Exception\Generic If any other database problem occurs
519
     */
520
    protected function doProjection(array $criteria, array $projections, \Caridea\Http\Pagination $pagination = null, bool $totalCount = false): iterable
521
    {
522
        $total = null;
523
        if ($totalCount === true && $pagination !== null && ($pagination->getMax() != PHP_INT_MAX || $pagination->getOffset() > 0)) {
524
            $total = $this->countAll($criteria);
525
        }
526
        $results = $this->doExecute(function (Manager $m, string $c) use ($criteria, $projections, $pagination) {
527
            $qo = [];
528
            if ($pagination !== null) {
529
                if ($pagination->getMax() != PHP_INT_MAX) {
530
                    $qo['limit'] = $pagination->getMax();
531
                }
532
                $qo['skip'] = $pagination->getOffset();
533
                $sorts = [];
534
                foreach ($pagination->getOrder() as $k => $v) {
535
                    $sorts[$k] = $v ? 1 : -1;
536
                }
537
                if (count($sorts) > 0) {
538
                    $qo['sort'] = $sorts;
539
                }
540
            }
541
            if (!$projections->isEmpty()) {
0 ignored issues
show
The method isEmpty cannot be called on $projections (of type array<string,*>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
542
                $qo['projection'] = $projections;
543
            }
544
            $q = new \MongoDB\Driver\Query($criteria, $qo);
545
            return $m->executeQuery($c, $q, $this->readPreference);
546
        });
547
        return $total === null ? $results : new CursorSubset($results, $total);
548
    }
549
550
    /**
551
     * Possibly add the entity to the cache.
552
     *
553
     * @param $entity - The entity to possibly cache
554
     * @return - The same entity that came in
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
555
     */
556
    protected function maybeCache($entity)
557
    {
558
        if ($this->caching && $entity !== null) {
559
            $id = (string) Getter::getId($entity);
560
            if (!array_key_exists($id, $this->cache)) {
561
                $this->cache[$id] = $entity;
562
            }
563
        }
564
        return $entity;
565
    }
566
567
    /**
568
     * Possibly add entities to the cache.
569
     *
570
     * @param $entities - The entities to possibly cache
571
     * @return - The same entities that came in
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
572
     */
573
    protected function maybeCacheAll(iterable $entities): iterable
574
    {
575
        if ($this->caching) {
576
            $results = $entities instanceof \MongoDB\Driver\Cursor ?
0 ignored issues
show
The class MongoDB\Driver\Cursor 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...
577
                $entities->toArray() : (is_array($entities) ? $entities : iterator_to_array($entities, false));
578
            foreach ($results as $entity) {
579
                if ($entity !== null) {
580
                    $id = (string) Getter::getId($entity);
581
                    if (!array_key_exists($id, $this->cache)) {
582
                        $this->cache[$id] = $entity;
583
                    }
584
                }
585
            }
586
            return $results;
587
        } else {
588
            return $entities;
589
        }
590
    }
591
592
    /**
593
     * Gets an entry from the cache
594
     *
595
     * @param $id - The cache key
596
     * @return - The entity found or null
0 ignored issues
show
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
597
     */
598
    protected function getFromCache(string $id)
599
    {
600
        return $this->cache[$id] ?? null;
601
    }
602
}
603