Issues (353)

Security Analysis    no request data  

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.

lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php (4 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
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Aggregation;
6
7
use Doctrine\ODM\MongoDB\DocumentManager;
8
use Doctrine\ODM\MongoDB\Iterator\CachingIterator;
9
use Doctrine\ODM\MongoDB\Iterator\HydratingIterator;
10
use Doctrine\ODM\MongoDB\Iterator\Iterator;
11
use Doctrine\ODM\MongoDB\Iterator\UnrewindableIterator;
12
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
13
use Doctrine\ODM\MongoDB\Persisters\DocumentPersister;
14
use Doctrine\ODM\MongoDB\Query\Expr as QueryExpr;
15
use GeoJson\Geometry\Point;
16
use MongoDB\Collection;
17
use MongoDB\Driver\Cursor;
18
use OutOfRangeException;
19
use function array_map;
20
use function array_merge;
21
use function array_unshift;
22
use function assert;
23
use function is_array;
24
use function sprintf;
25
26
/**
27
 * Fluent interface for building aggregation pipelines.
28
 */
29
class Builder
30
{
31
    /**
32
     * The DocumentManager instance for this query
33
     *
34
     * @var DocumentManager
35
     */
36
    private $dm;
37
38
    /**
39
     * The ClassMetadata instance.
40
     *
41
     * @var ClassMetadata
42
     */
43
    private $class;
44
45
    /** @var string */
46
    private $hydrationClass;
47
48
    /**
49
     * The Collection instance.
50
     *
51
     * @var Collection
52
     */
53
    private $collection;
54
55
    /** @var Stage[] */
56
    private $stages = [];
57
58
    /** @var bool */
59
    private $rewindable = true;
60
61
    /**
62
     * Create a new aggregation builder.
63
     */
64 269
    public function __construct(DocumentManager $dm, string $documentName)
65
    {
66 269
        $this->dm         = $dm;
67 269
        $this->class      = $this->dm->getClassMetadata($documentName);
68 269
        $this->collection = $this->dm->getDocumentCollection($documentName);
69 269
    }
70
71
    /**
72
     * Adds new fields to documents. $addFields outputs documents that contain all
73
     * existing fields from the input documents and newly added fields.
74
     *
75
     * The $addFields stage is equivalent to a $project stage that explicitly specifies
76
     * all existing fields in the input documents and adds the new fields.
77
     *
78
     * If the name of the new field is the same as an existing field name (including _id),
79
     * $addFields overwrites the existing value of that field with the value of the
80
     * specified expression.
81
     *
82
     * @see http://docs.mongodb.com/manual/reference/operator/aggregation/addFields/
83
     */
84 1
    public function addFields() : Stage\AddFields
85
    {
86 1
        $stage = new Stage\AddFields($this);
87 1
        $this->addStage($stage);
88
89 1
        return $stage;
90
    }
91
92
    /**
93
     * Categorizes incoming documents into groups, called buckets, based on a
94
     * specified expression and bucket boundaries.
95
     *
96
     * Each bucket is represented as a document in the output. The document for
97
     * each bucket contains an _id field, whose value specifies the inclusive
98
     * lower bound of the bucket and a count field that contains the number of
99
     * documents in the bucket. The count field is included by default when the
100
     * output is not specified.
101
     *
102
     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/bucket/
103
     */
104 2
    public function bucket() : Stage\Bucket
105
    {
106 2
        $stage = new Stage\Bucket($this, $this->dm, $this->class);
107 2
        $this->addStage($stage);
108
109 2
        return $stage;
110
    }
111
112
    /**
113
     * Categorizes incoming documents into a specific number of groups, called
114
     * buckets, based on a specified expression.
115
     *
116
     * Bucket boundaries are automatically determined in an attempt to evenly
117
     * distribute the documents into the specified number of buckets. Each
118
     * bucket is represented as a document in the output. The document for each
119
     * bucket contains an _id field, whose value specifies the inclusive lower
120
     * bound and the exclusive upper bound for the bucket, and a count field
121
     * that contains the number of documents in the bucket. The count field is
122
     * included by default when the output is not specified.
123
     *
124
     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/
125
     */
126 2
    public function bucketAuto() : Stage\BucketAuto
127
    {
128 2
        $stage = new Stage\BucketAuto($this, $this->dm, $this->class);
129 2
        $this->addStage($stage);
130
131 2
        return $stage;
132
    }
133
134
    /**
135
     * Returns statistics regarding a collection or view.
136
     *
137
     * $collStats must be the first stage in an aggregation pipeline, or else
138
     * the pipeline returns an error.
139
     *
140
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/collStats/
141
     */
142 1
    public function collStats() : Stage\CollStats
143
    {
144 1
        $stage = new Stage\CollStats($this);
145 1
        $this->addStage($stage);
146
147 1
        return $stage;
148
    }
149
150
    /**
151
     * Returns a document that contains a count of the number of documents input
152
     * to the stage.
153
     *
154
     * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/
155
     */
156 1
    public function count(string $fieldName) : Stage\Count
157
    {
158 1
        $stage = new Stage\Count($this, $fieldName);
159 1
        $this->addStage($stage);
160
161 1
        return $stage;
162
    }
163
164
    /**
165
     * Executes the aggregation pipeline
166
     */
167 20
    public function execute(array $options = []) : Iterator
168
    {
169
        // Force cursor to be used
170 20
        $options = array_merge($options, ['cursor' => true]);
171
172 20
        $cursor = $this->collection->aggregate($this->getPipeline(), $options);
173 20
        assert($cursor instanceof Cursor);
174
175 20
        return $this->prepareIterator($cursor);
176
    }
177
178 158
    public function expr() : Expr
179
    {
180 158
        return new Expr($this->dm, $this->class);
181
    }
182
183
    /**
184
     * Processes multiple aggregation pipelines within a single stage on the
185
     * same set of input documents.
186
     *
187
     * Each sub-pipeline has its own field in the output document where its
188
     * results are stored as an array of documents.
189
     */
190 1
    public function facet() : Stage\Facet
191
    {
192 1
        $stage = new Stage\Facet($this);
193 1
        $this->addStage($stage);
194
195 1
        return $stage;
196
    }
197
198
    /**
199
     * Outputs documents in order of nearest to farthest from a specified point.
200
     *
201
     * A GeoJSON point may be provided as the first and only argument for
202
     * 2dsphere queries. This single parameter may be a GeoJSON point object or
203
     * an array corresponding to the point's JSON representation. If GeoJSON is
204
     * used, the "spherical" option will default to true.
205
     *
206
     * You can only use this as the first stage of a pipeline.
207
     *
208
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/geoNear/
209
     *
210
     * @param float|array|Point $x
211
     * @param float             $y
212
     */
213 4
    public function geoNear($x, $y = null) : Stage\GeoNear
214
    {
215 4
        $stage = new Stage\GeoNear($this, $x, $y);
216 4
        $this->addStage($stage);
217
218 4
        return $stage;
219
    }
220
221
    /**
222
     * Returns the assembled aggregation pipeline
223
     *
224
     * For pipelines where the first stage is a $geoNear stage, it will apply
225
     * the document filters and discriminator queries to the query portion of
226
     * the geoNear operation. For all other pipelines, it prepends a $match stage
227
     * containing the required query.
228
     */
229 70
    public function getPipeline() : array
230
    {
231 70
        $pipeline = array_map(
232
            static function (Stage $stage) {
233 70
                return $stage->getExpression();
234 70
            },
235 70
            $this->stages
236
        );
237
238 70
        if ($this->getStage(0) instanceof Stage\GeoNear) {
239 4
            $pipeline[0]['$geoNear']['query'] = $this->applyFilters($pipeline[0]['$geoNear']['query']);
240 66
        } elseif ($this->getStage(0) instanceof Stage\IndexStats) {
241
            // Don't apply any filters when using an IndexStats stage: since it
242
            // needs to be the first pipeline stage, prepending a match stage
243
            // with discriminator information will not work
244
245 2
            return $pipeline;
246
        } else {
247 64
            $matchExpression = $this->applyFilters([]);
248 64
            if ($matchExpression !== []) {
249 1
                array_unshift($pipeline, ['$match' => $matchExpression]);
250
            }
251
        }
252
253 68
        return $pipeline;
254
    }
255
256
    /**
257
     * Returns a certain stage from the pipeline
258
     */
259 70
    public function getStage(int $index) : Stage
260
    {
261 70
        if (! isset($this->stages[$index])) {
262
            throw new OutOfRangeException(sprintf('Could not find stage with index %d.', $index));
263
        }
264
265 70
        return $this->stages[$index];
266
    }
267
268
    /**
269
     * Performs a recursive search on a collection, with options for restricting
270
     * the search by recursion depth and query filter.
271
     *
272
     * @see https://docs.mongodb.org/manual/reference/operator/aggregation/graphLookup/
273
     *
274
     * @param string $from Target collection for the $graphLookup operation to
275
     * search, recursively matching the connectFromField to the connectToField.
276
     */
277 10 View Code Duplication
    public function graphLookup(string $from) : Stage\GraphLookup
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
    {
279 10
        $stage = new Stage\GraphLookup($this, $from, $this->dm, $this->class);
280 9
        $this->addStage($stage);
281
282 9
        return $stage;
283
    }
284
285
    /**
286
     * Groups documents by some specified expression and outputs to the next
287
     * stage a document for each distinct grouping.
288
     *
289
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/group/
290
     */
291 4
    public function group() : Stage\Group
292
    {
293 4
        $stage = new Stage\Group($this);
294 4
        $this->addStage($stage);
295
296 4
        return $stage;
297
    }
298
299
    /**
300
     * Set which class to use when hydrating results as document class instances.
301
     */
302 4
    public function hydrate(string $className) : self
303
    {
304 4
        $this->hydrationClass = $className;
305
306 4
        return $this;
307
    }
308
309
    /**
310
     * Returns statistics regarding the use of each index for the collection.
311
     *
312
     * @see https://docs.mongodb.org/manual/reference/operator/aggregation/indexStats/
313
     */
314 2
    public function indexStats() : Stage\IndexStats
315
    {
316 2
        $stage = new Stage\IndexStats($this);
317 2
        $this->addStage($stage);
318
319 2
        return $stage;
320
    }
321
322
    /**
323
     * Limits the number of documents passed to the next stage in the pipeline.
324
     *
325
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/limit/
326
     */
327 2
    public function limit(int $limit) : Stage\Limit
328
    {
329 2
        $stage = new Stage\Limit($this, $limit);
330 2
        $this->addStage($stage);
331
332 2
        return $stage;
333
    }
334
335
    /**
336
     * Performs a left outer join to an unsharded collection in the same
337
     * database to filter in documents from the “joined” collection for
338
     * processing.
339
     *
340
     * @see https://docs.mongodb.org/manual/reference/operator/aggregation/lookup/
341
     */
342 16 View Code Duplication
    public function lookup(string $from) : Stage\Lookup
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
343
    {
344 16
        $stage = new Stage\Lookup($this, $from, $this->dm, $this->class);
345 14
        $this->addStage($stage);
346
347 14
        return $stage;
348
    }
349
350
    /**
351
     * Filters the documents to pass only the documents that match the specified
352
     * condition(s) to the next pipeline stage.
353
     *
354
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/match/
355
     */
356 9
    public function match() : Stage\Match
357
    {
358 9
        $stage = new Stage\Match($this);
359 9
        $this->addStage($stage);
360
361 9
        return $stage;
362
    }
363
364
    /**
365
     * Returns a query expression to be used in match stages
366
     */
367 61
    public function matchExpr() : QueryExpr
368
    {
369 61
        $expr = new QueryExpr($this->dm);
370 61
        $expr->setClassMetadata($this->class);
371
372 61
        return $expr;
373
    }
374
375
    /**
376
     * Takes the documents returned by the aggregation pipeline and writes them
377
     * to a specified collection. This must be the last stage in the pipeline.
378
     *
379
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/out/
380
     */
381 6
    public function out(string $from) : Stage\Out
382
    {
383 6
        $stage = new Stage\Out($this, $from, $this->dm);
384 5
        $this->addStage($stage);
385
386 5
        return $stage;
387
    }
388
389
    /**
390
     * Passes along the documents with only the specified fields to the next
391
     * stage in the pipeline. The specified fields can be existing fields from
392
     * the input documents or newly computed fields.
393
     *
394
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/project/
395
     */
396 15
    public function project() : Stage\Project
397
    {
398 15
        $stage = new Stage\Project($this);
399 15
        $this->addStage($stage);
400
401 15
        return $stage;
402
    }
403
404
    /**
405
     * Restricts the contents of the documents based on information stored in
406
     * the documents themselves.
407
     *
408
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/redact/
409
     */
410 2
    public function redact() : Stage\Redact
411
    {
412 2
        $stage = new Stage\Redact($this);
413 2
        $this->addStage($stage);
414
415 2
        return $stage;
416
    }
417
418
    /**
419
     * Promotes a specified document to the top level and replaces all other
420
     * fields.
421
     *
422
     * The operation replaces all existing fields in the input document,
423
     * including the _id field. You can promote an existing embedded document to
424
     * the top level, or create a new document for promotion.
425
     *
426
     * @param string|array|null $expression Optional. A replacement expression that
427
     * resolves to a document.
428
     */
429 6 View Code Duplication
    public function replaceRoot($expression = null) : Stage\ReplaceRoot
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
430
    {
431 6
        $stage = new Stage\ReplaceRoot($this, $this->dm, $this->class, $expression);
432 6
        $this->addStage($stage);
433
434 6
        return $stage;
435
    }
436
437
    /**
438
     * Controls if resulting iterator should be wrapped with CachingIterator.
439
     */
440 1
    public function rewindable(bool $rewindable = true) : self
441
    {
442 1
        $this->rewindable = $rewindable;
443
444 1
        return $this;
445
    }
446
447
    /**
448
     * Randomly selects the specified number of documents from its input.
449
     *
450
     * @see https://docs.mongodb.org/manual/reference/operator/aggregation/sample/
451
     */
452 2
    public function sample(int $size) : Stage\Sample
453
    {
454 2
        $stage = new Stage\Sample($this, $size);
455 2
        $this->addStage($stage);
456
457 2
        return $stage;
458
    }
459
460
    /**
461
     * Skips over the specified number of documents that pass into the stage and
462
     * passes the remaining documents to the next stage in the pipeline.
463
     *
464
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/skip/
465
     */
466 2
    public function skip(int $skip) : Stage\Skip
467
    {
468 2
        $stage = new Stage\Skip($this, $skip);
469 2
        $this->addStage($stage);
470
471 2
        return $stage;
472
    }
473
474
    /**
475
     * Sorts all input documents and returns them to the pipeline in sorted
476
     * order.
477
     *
478
     * If sorting by multiple fields, the first argument should be an array of
479
     * field name (key) and order (value) pairs.
480
     *
481
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/sort/
482
     *
483
     * @param array|string $fieldName Field name or array of field/order pairs
484
     * @param int|string   $order     Field order (if one field is specified)
485
     */
486 7
    public function sort($fieldName, $order = null) : Stage\Sort
487
    {
488 7
        $fields = is_array($fieldName) ? $fieldName : [$fieldName => $order];
489
        // fixme: move to sort stage
490 7
        $stage = new Stage\Sort($this, $this->getDocumentPersister()->prepareSort($fields));
491 7
        $this->addStage($stage);
492
493 7
        return $stage;
494
    }
495
496
    /**
497
     * Groups incoming documents based on the value of a specified expression,
498
     * then computes the count of documents in each distinct group.
499
     *
500
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/sortByCount/
501
     */
502 3 View Code Duplication
    public function sortByCount(string $expression) : Stage\SortByCount
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
503
    {
504 3
        $stage = new Stage\SortByCount($this, $expression, $this->dm, $this->class);
505 3
        $this->addStage($stage);
506
507 3
        return $stage;
508
    }
509
510
    /**
511
     * Deconstructs an array field from the input documents to output a document
512
     * for each element. Each output document is the input document with the
513
     * value of the array field replaced by the element.
514
     *
515
     * @see http://docs.mongodb.org/manual/reference/operator/aggregation/unwind/
516
     */
517 7
    public function unwind(string $fieldName) : Stage\Unwind
518
    {
519
        // Fixme: move field name translation to stage
520 7
        $stage = new Stage\Unwind($this, $this->getDocumentPersister()->prepareFieldName($fieldName));
521 7
        $this->addStage($stage);
522
523 7
        return $stage;
524
    }
525
526
    /**
527
     * Allows adding an arbitrary stage to the pipeline
528
     *
529
     * @return Stage The method returns the stage given as an argument
530
     */
531 76
    public function addStage(Stage $stage) : Stage
532
    {
533 76
        $this->stages[] = $stage;
534
535 76
        return $stage;
536
    }
537
538
    /**
539
     * Applies filters and discriminator queries to the pipeline
540
     */
541 68
    private function applyFilters(array $query) : array
542
    {
543 68
        $documentPersister = $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
544
545 68
        $query = $documentPersister->addDiscriminatorToPreparedQuery($query);
546 68
        $query = $documentPersister->addFilterToPreparedQuery($query);
547
548 68
        return $query;
549
    }
550
551 11
    private function getDocumentPersister() : DocumentPersister
552
    {
553 11
        return $this->dm->getUnitOfWork()->getDocumentPersister($this->class->name);
554
    }
555
556 20
    private function prepareIterator(Cursor $cursor) : Iterator
557
    {
558 20
        $class = null;
559 20
        if ($this->hydrationClass) {
560 4
            $class = $this->dm->getClassMetadata($this->hydrationClass);
561
        }
562
563 20
        if ($class) {
564 4
            $cursor = new HydratingIterator($cursor, $this->dm->getUnitOfWork(), $class);
565
        }
566
567 20
        $cursor = $this->rewindable ? new CachingIterator($cursor) : new UnrewindableIterator($cursor);
568
569 20
        return $cursor;
570
    }
571
}
572