Skip to content

Commit cdae043

Browse files
authored
Merge pull request doctrine#2160 from Ludo444/bug/2157
Bug/2157 Test (and fix) Facet's aggregation pipeline with DiscriminatorMap documents
2 parents 9138ec2 + 58a92a2 commit cdae043

File tree

3 files changed

+112
-10
lines changed

3 files changed

+112
-10
lines changed

lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php

+41-9
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
use MongoDB\Collection;
1616
use MongoDB\Driver\Cursor;
1717
use OutOfRangeException;
18+
use TypeError;
1819
use function array_map;
1920
use function array_merge;
2021
use function array_unshift;
2122
use function assert;
23+
use function func_get_arg;
24+
use function func_num_args;
25+
use function gettype;
2226
use function is_array;
27+
use function is_bool;
2328
use function sprintf;
2429

2530
/**
@@ -214,36 +219,63 @@ public function geoNear($x, $y = null) : Stage\GeoNear
214219
return $stage;
215220
}
216221

222+
// phpcs:disable Squiz.Commenting.FunctionComment.ExtraParamComment
217223
/**
218224
* Returns the assembled aggregation pipeline
219225
*
226+
* @param bool $applyFilters Whether to apply filters on the aggregation
227+
* pipeline stage
228+
*
220229
* For pipelines where the first stage is a $geoNear stage, it will apply
221230
* the document filters and discriminator queries to the query portion of
222231
* the geoNear operation. For all other pipelines, it prepends a $match stage
223232
* containing the required query.
233+
*
234+
* For aggregation pipelines that will be nested (e.g. in a facet stage),
235+
* you should not apply filters as this may cause wrong results to be
236+
* given.
224237
*/
225-
public function getPipeline() : array
238+
// phpcs:enable Squiz.Commenting.FunctionComment.ExtraParamComment
239+
public function getPipeline(/* bool $applyFilters = true */) : array
226240
{
241+
$applyFilters = func_num_args() > 0 ? func_get_arg(0) : true;
242+
243+
if (! is_bool($applyFilters)) {
244+
throw new TypeError(sprintf(
245+
'Argument 1 passed to %s must be of the type bool, %s given',
246+
__METHOD__,
247+
gettype($applyFilters)
248+
));
249+
}
250+
227251
$pipeline = array_map(
228252
static function (Stage $stage) {
229253
return $stage->getExpression();
230254
},
231255
$this->stages
232256
);
233257

234-
if ($this->getStage(0) instanceof Stage\GeoNear) {
235-
$pipeline[0]['$geoNear']['query'] = $this->applyFilters($pipeline[0]['$geoNear']['query']);
236-
} elseif ($this->getStage(0) instanceof Stage\IndexStats) {
258+
if ($this->getStage(0) instanceof Stage\IndexStats) {
237259
// Don't apply any filters when using an IndexStats stage: since it
238260
// needs to be the first pipeline stage, prepending a match stage
239261
// with discriminator information will not work
240262

263+
$applyFilters = false;
264+
}
265+
266+
if (! $applyFilters) {
241267
return $pipeline;
242-
} else {
243-
$matchExpression = $this->applyFilters([]);
244-
if ($matchExpression !== []) {
245-
array_unshift($pipeline, ['$match' => $matchExpression]);
246-
}
268+
}
269+
270+
if ($this->getStage(0) instanceof Stage\GeoNear) {
271+
$pipeline[0]['$geoNear']['query'] = $this->applyFilters($pipeline[0]['$geoNear']['query']);
272+
273+
return $pipeline;
274+
}
275+
276+
$matchExpression = $this->applyFilters([]);
277+
if ($matchExpression !== []) {
278+
array_unshift($pipeline, ['$match' => $matchExpression]);
247279
}
248280

249281
return $pipeline;

lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Facet.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function getExpression() : array
2828
{
2929
return [
3030
'$facet' => array_map(static function (Builder $builder) {
31-
return $builder->getPipeline();
31+
return $builder->getPipeline(false);
3232
}, $this->pipelines),
3333
];
3434
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\ODM\Tests\Functional\Ticket;
6+
7+
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
8+
use Doctrine\ODM\MongoDB\Tests\BaseTest;
9+
use function count;
10+
11+
class GH2157Test extends BaseTest
12+
{
13+
public function testFacetDiscriminatorMapCreation()
14+
{
15+
$this->dm->persist(new GH2157FirstType());
16+
$this->dm->persist(new GH2157FirstType());
17+
$this->dm->persist(new GH2157FirstType());
18+
$this->dm->persist(new GH2157FirstType());
19+
$this->dm->flush();
20+
21+
$result = $this->dm->createAggregationBuilder(GH2157FirstType::class)
22+
->project()
23+
->includeFields(['id'])
24+
->facet()
25+
->field('count')
26+
->pipeline(
27+
$this->dm->createAggregationBuilder(GH2157FirstType::class)
28+
->count('count')
29+
)
30+
->field('limitedResults')
31+
->pipeline(
32+
$this->dm->createAggregationBuilder(GH2157FirstType::class)
33+
->limit(2)
34+
)
35+
->execute()->toArray();
36+
37+
$this->assertEquals(4, $result[0]['count'][0]['count']);
38+
$this->assertEquals(2, count($result[0]['limitedResults']));
39+
}
40+
}
41+
42+
/**
43+
* @ODM\Document(collection="documents")
44+
* @ODM\InheritanceType("SINGLE_COLLECTION")
45+
* @ODM\DiscriminatorField("type")
46+
* @ODM\DiscriminatorMap({"firsttype"=GH2157FirstType::class, "secondtype"=GH2157SecondType::class})
47+
*/
48+
abstract class GH2157Abstract
49+
{
50+
/**
51+
* @ODM\Id
52+
*
53+
* @var string
54+
*/
55+
protected $id;
56+
}
57+
58+
/**
59+
* @ODM\Document
60+
*/
61+
class GH2157FirstType extends GH2157Abstract
62+
{
63+
}
64+
65+
/**
66+
* @ODM\Document
67+
*/
68+
class GH2157SecondType extends GH2157Abstract
69+
{
70+
}

0 commit comments

Comments
 (0)