Skip to content

Commit 00d2548

Browse files
committed
add group operation
1 parent b795377 commit 00d2548

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

src/Collection.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace MongoDB;
44

5+
use MongoDB\BSON\Javascript;
56
use MongoDB\Driver\Command;
67
use MongoDB\Driver\Cursor;
78
use MongoDB\Driver\Manager;
@@ -26,6 +27,7 @@
2627
use MongoDB\Operation\FindOneAndDelete;
2728
use MongoDB\Operation\FindOneAndReplace;
2829
use MongoDB\Operation\FindOneAndUpdate;
30+
use MongoDB\Operation\Group;
2931
use MongoDB\Operation\InsertMany;
3032
use MongoDB\Operation\InsertOne;
3133
use MongoDB\Operation\ListIndexes;
@@ -442,6 +444,23 @@ public function getNamespace()
442444
return $this->databaseName . '.' . $this->collectionName;
443445
}
444446

447+
/**
448+
*
449+
* @see Grop::__construct()
450+
* @param mixed $keys Fields to group by. If an array or non-code object is passed, it will be the key used to group results.
451+
* @param array $initial Initializes the aggregation result document.
452+
* @param Javascript $reduce An aggregation function that operates on the documents during the grouping operation.
453+
* @param array $options Command options
454+
* @return Traversable
455+
*/
456+
public function group($keys, array $initial, Javascript $reduce, array $options = [])
457+
{
458+
$operation = new Group($this->databaseName, $this->collectionName, $keys, $initial, $reduce, $options);
459+
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
460+
461+
return $operation->execute($server);
462+
}
463+
445464
/**
446465
* Inserts multiple documents.
447466
*

src/Operation/Group.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace MongoDB\Operation;
4+
5+
6+
use MongoDB\BSON\Javascript;
7+
use MongoDB\Driver\Command;
8+
use MongoDB\Driver\Server;
9+
use \ArrayIterator;
10+
use \Traversable;
11+
use MongoDB\Exception\InvalidArgumentTypeException;
12+
use MongoDB\Exception\RuntimeException;
13+
use MongoDB\Exception\UnexpectedValueException;
14+
15+
class Group implements Executable
16+
{
17+
public $collectionName;
18+
public $databaseName;
19+
public $keys;
20+
public $initial;
21+
public $reduce;
22+
public $options;
23+
24+
/**
25+
* Group constructor.
26+
* @param string $databaseName
27+
* @param string $collectionName The collection from which to perform the group by operation.
28+
* @param mixed $keys Fields to group by. If an array or non-code object is passed, it will be the key used to group results.
29+
* @param array $initial Initializes the aggregation result document.
30+
* @param array $options Other optional fields
31+
* @param Javascript $reduce An aggregation function that operates on the documents during the grouping operation.
32+
*/
33+
public function __construct($databaseName, $collectionName, $keys, array $initial, Javascript $reduce, array $options = [])
34+
{
35+
36+
if (isset($options['finalize']) && !$options['finalize'] instanceof Javascript) {
37+
throw new InvalidArgumentTypeException('"finalize" option', $options['finalize'], 'MongoDB\BSON\Javascript');
38+
}
39+
40+
if (isset($options['cond']) && !is_array($options['cond']) && !is_object($options['cond'])) {
41+
throw new InvalidArgumentTypeException('"cond" option', $options['cond'], 'array or object');
42+
}
43+
44+
if (!is_array($keys) && !is_object($keys) && !$keys instanceof Javascript) {
45+
throw new InvalidArgumentTypeException('"keys" option', $keys, 'array, object or instance of MongoDB\BSON\Javascript');
46+
}
47+
48+
$this->collectionName = (string) $collectionName;
49+
$this->databaseName = (string) $databaseName;
50+
$this->keys = $keys;
51+
$this->initial = (object) $initial;
52+
$this->reduce = $reduce;
53+
$this->options = $options;
54+
}
55+
56+
/**
57+
* Execute the operation.
58+
*
59+
* @see Executable::execute()
60+
* @param Server $server
61+
* @return Traversable
62+
*/
63+
public function execute(Server $server)
64+
{
65+
$key = $this->keys instanceof Javascript ? '$keyf' : 'key';
66+
$cmd = [
67+
'group' => [
68+
$key => $this->keys,
69+
'ns' => $this->collectionName,
70+
'$reduce' => $this->reduce,
71+
'initial' => $this->initial
72+
] + $this->options
73+
];
74+
75+
$cursor = $server->executeCommand($this->databaseName, new Command($cmd));
76+
$result = current($cursor->toArray());
77+
78+
if (empty($result->ok)) {
79+
throw new RuntimeException(isset($result->errmsg) ? $result->errmsg : 'Unknown error');
80+
}
81+
82+
if ( ! isset($result->retval) || ! is_array($result->retval)) {
83+
throw new UnexpectedValueException('group command did not return a "retval" array');
84+
}
85+
return new ArrayIterator($result->retval);
86+
87+
}
88+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\Collection\CrudSpec;
4+
5+
use MongoDB\BSON\Javascript;
6+
use MongoDB\Driver\BulkWrite;
7+
8+
/**
9+
* CRUD spec functional tests for group().
10+
*
11+
* @see https://github.com/mongodb/specifications/tree/master/source/crud/tests
12+
*/
13+
class GroupFunctionalTest extends FunctionalTestCase
14+
{
15+
public function setUp()
16+
{
17+
parent::setUp();
18+
19+
$this->createFixtures(3);
20+
21+
$bulkWrite = new BulkWrite(['ordered' => true]);
22+
23+
$bulkWrite->insert([
24+
'_id' => 4,
25+
'x' => 11,
26+
]);
27+
$this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);
28+
}
29+
30+
public function testGroup()
31+
{
32+
$reduce = new Javascript("function (curr, result) {result.total++}");
33+
34+
$result = $this->collection->group(['x' => 1], ['total' => 0], $reduce, ['cond' => ['x' => ['$lt' => 22]]]);
35+
36+
$expected = [
37+
['x' => 11, 'total' => 2],
38+
];
39+
40+
$this->assertSameDocuments($expected, $result);
41+
}
42+
43+
public function testGroupWithKeyFunction()
44+
{
45+
$keyf = new Javascript("function(doc) { return {calculated: doc.x} }");
46+
$reduce = new Javascript("function (curr, result) {}");
47+
48+
$result = $this->collection->group($keyf, [], $reduce, ['cond' => ['x' => ['$lt' => 22]]]);
49+
50+
$expected = [
51+
['calculated' => 11],
52+
];
53+
54+
$this->assertSameDocuments($expected, $result);
55+
}
56+
}

tests/Collection/FunctionalTestCase.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
*/
1212
abstract class FunctionalTestCase extends BaseFunctionalTestCase
1313
{
14+
/**
15+
* @var Collection
16+
*/
1417
protected $collection;
1518

1619
public function setUp()

0 commit comments

Comments
 (0)