Skip to content

Commit 33b3dc0

Browse files
committed
Partial work on aggregation
1 parent e45c919 commit 33b3dc0

File tree

9 files changed

+255
-27
lines changed

9 files changed

+255
-27
lines changed

src/Client.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tequila\MongoDB;
44

55
use MongoDB\Driver\Manager;
6+
use Tequila\MongoDB\Command\CommandInterface;
67
use Tequila\MongoDB\Command\DropDatabase;
78
use Tequila\MongoDB\Command\ListDatabases;
89
use Tequila\MongoDB\Command\Result\DatabaseInfo;
@@ -33,7 +34,7 @@ class Client
3334
* @param array $uriOptions
3435
* @param array $driverOptions
3536
*/
36-
public function __construct($uri = 'mongodb://localhost:27017', array $uriOptions = [], array $driverOptions = [])
37+
public function __construct($uri = 'mongodb://127.0.0.1/', array $uriOptions = [], array $driverOptions = [])
3738
{
3839
$uriOptions = ConnectionOptions::resolve($uriOptions);
3940
$driverOptions = DriverOptions::resolve($driverOptions);
@@ -75,10 +76,19 @@ public function listDatabases()
7576
}
7677

7778
throw new UnexpectedResultException(
78-
'Command listDatabases did not return expected "databases" array'
79+
'Command "listDatabases" did not return expected "databases" array'
7980
);
8081
}
8182

83+
/**
84+
* @param CommandInterface $command
85+
* @return \MongoDB\Driver\Cursor
86+
*/
87+
public function runCommand(CommandInterface $command)
88+
{
89+
return $command->execute($this->manager);
90+
}
91+
8292
/**
8393
* @param string $databaseName
8494
* @param string $collectionName

src/Collection.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
use MongoDB\Driver\ReadConcern;
77
use MongoDB\Driver\ReadPreference;
88
use MongoDB\Driver\WriteConcern;
9+
use Tequila\MongoDB\Command\Aggregate;
910
use Tequila\MongoDB\Command\CreateIndexes;
1011
use Tequila\MongoDB\Command\DropCollection;
1112
use Tequila\MongoDB\Command\DropIndexes;
1213
use Tequila\MongoDB\Command\ListIndexes;
14+
use Tequila\MongoDB\Exception\InvalidArgumentException;
1315
use Tequila\MongoDB\Operation\Find;
1416
use Tequila\MongoDB\Options\DatabaseAndCollectionOptions;
1517
use Tequila\MongoDB\Options\Driver\TypeMapOptions;
@@ -83,15 +85,36 @@ public function __construct(Manager $manager, $databaseName, $collectionName, ar
8385
$this->typeMap = $options['typeMap'];
8486
}
8587

88+
/**
89+
* @param array $pipeline
90+
* @param array $options
91+
* @return \MongoDB\Driver\Cursor aggregation cursor
92+
*/
8693
public function aggregate(array $pipeline, array $options = [])
8794
{
8895
$defaults = [
8996
'readConcern' => $this->readConcern,
9097
'readPreference' => $this->readPreference,
91-
'typeMap' => $this->typeMap,
9298
];
9399

94-
// TODO finish method
100+
if (isset($options['typeMap'])) {
101+
if (!is_array($options['typeMap'])) {
102+
throw new InvalidArgumentException('Option "typeMap" must be an array');
103+
}
104+
105+
$typeMap = TypeMapOptions::resolve($options['typeMap']);
106+
unset($options['typeMap']);
107+
}
108+
109+
$options += $defaults;
110+
$command = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
111+
$cursor = $command->execute($this->manager);
112+
113+
if (isset($typeMap)) {
114+
$cursor->setTypeMap($typeMap);
115+
}
116+
117+
return $cursor;
95118
}
96119

97120
/**

src/Command/Aggregate.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace Tequila\MongoDB\Command;
4+
5+
use MongoDB\Driver\Command;
6+
use MongoDB\Driver\Manager;
7+
use MongoDB\Driver\ReadConcern;
8+
use MongoDB\Driver\ReadPreference;
9+
use Tequila\MongoDB\Command\Options\AggregateOptions;
10+
11+
class Aggregate implements CommandInterface
12+
{
13+
/**
14+
* @var string
15+
*/
16+
private $databaseName;
17+
18+
/**
19+
* @var string
20+
*/
21+
private $collectionName;
22+
23+
/**
24+
* @var array
25+
*/
26+
private $pipeline;
27+
28+
/**
29+
* @var array
30+
*/
31+
private $options;
32+
33+
/**
34+
* @param string $databaseName
35+
* @param string $collectionName
36+
* @param array $pipeline
37+
* @param array $options
38+
*/
39+
public function __construct($databaseName, $collectionName, array $pipeline, array $options = [])
40+
{
41+
$this->databaseName = (string)$databaseName;
42+
$this->collectionName = (string)$collectionName;
43+
$this->pipeline = $pipeline;
44+
$this->options = AggregateOptions::resolve($options);
45+
}
46+
47+
public function execute(Manager $manager)
48+
{
49+
if (isset($this->options['readConcern'])) {
50+
/** @var ReadConcern $readConcern */
51+
$readConcern = $this->options['readConcern'];
52+
if ($this->hasOutStage() && ReadConcern::MAJORITY === $readConcern->getLevel()) {
53+
unset($this->options['readConcern']);
54+
} else {
55+
$this->options['readConcern'] = ['level' => $readConcern->getLevel()];
56+
}
57+
}
58+
59+
if ($this->hasOutStage()) {
60+
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
61+
} else {
62+
if (isset($this->options['readPreference'])) {
63+
$readPreference = $this->options['readPreference'];
64+
} else {
65+
$readPreference = null;
66+
}
67+
}
68+
69+
unset($this->options['readPreference']);
70+
71+
$options = ['aggregate' => $this->collectionName, 'pipeline' => $this->pipeline];
72+
$options += $this->options;
73+
$command = new Command($options);
74+
75+
return $manager->executeCommand($this->databaseName, $command, $readPreference);
76+
}
77+
78+
private function hasOutStage()
79+
{
80+
$lastStage = end($this->pipeline);
81+
82+
return '$out' === key($lastStage);
83+
}
84+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Tequila\MongoDB\Command\Options;
4+
5+
use MongoDB\Driver\ReadConcern;
6+
use MongoDB\Driver\ReadPreference;
7+
use Symfony\Component\OptionsResolver\Options;
8+
use Symfony\Component\OptionsResolver\OptionsResolver;
9+
use Tequila\MongoDB\Options\OptionsInterface;
10+
use Tequila\MongoDB\Options\Traits\CachedResolverTrait;
11+
12+
class AggregateOptions implements OptionsInterface
13+
{
14+
use CachedResolverTrait {
15+
CachedResolverTrait::resolve as resolveOptions;
16+
}
17+
18+
/**
19+
* @param array $options
20+
* @return array
21+
*/
22+
public static function resolve(array $options)
23+
{
24+
$options = self::resolveOptions($options);
25+
unset($options['batchSize']);
26+
27+
return $options;
28+
}
29+
30+
public static function configureOptions(OptionsResolver $resolver)
31+
{
32+
$resolver->setDefined([
33+
'allowDiskUse',
34+
'batchSize',
35+
'bypassDocumentValidation',
36+
'cursor',
37+
'maxTimeMS',
38+
'readConcern',
39+
'readPreference',
40+
]);
41+
42+
$resolver
43+
->setAllowedTypes('allowDiskUse', 'bool')
44+
->setAllowedTypes('batchSize', 'integer')
45+
->setAllowedTypes('bypassDocumentValidation', 'bool')
46+
->setAllowedTypes('cursor', ['array', 'object'])
47+
->setAllowedTypes('maxTimeMS', 'integer')
48+
->setAllowedTypes('readConcern', ReadConcern::class)
49+
->setAllowedTypes('readPreference', ReadPreference::class);
50+
51+
$resolver->setDefault('cursor', new \stdClass());
52+
53+
$resolver->setNormalizer('cursor', function(Options $options, $cursorOptions) {
54+
$cursorOptions = (array)$cursorOptions;
55+
if (isset($options['batchSize'])) {
56+
$cursorOptions['batchSize'] = $options['batchSize'];
57+
}
58+
59+
return (object)$cursorOptions;
60+
});
61+
62+
$resolver->setNormalizer('readConcern', function(Options $options, ReadConcern $readConcern) {
63+
if (null === $readConcern->getLevel()) {
64+
return null; // mark this option to be deleted during call to resolve()
65+
}
66+
67+
return $readConcern;
68+
});
69+
}
70+
}

src/Operation/Options/FindOptions.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
use MongoDB\Driver\ReadConcern;
66
use MongoDB\Driver\ReadPreference;
77
use Symfony\Component\OptionsResolver\OptionsResolver;
8-
use Tequila\MongoDB\Options\OptionsInterface;
98
use Tequila\MongoDB\Options\Driver\TypeMapOptions;
9+
use Tequila\MongoDB\Options\OptionsInterface;
1010
use Tequila\MongoDB\Options\Traits\CachedResolverTrait;
1111

1212
class FindOptions implements OptionsInterface
@@ -64,7 +64,6 @@ public static function resolve(array $options)
6464

6565
public static function configureOptions(OptionsResolver $resolver)
6666
{
67-
TypeMapOptions::configureOptions($resolver);
6867
$resolver->setDefined([
6968
'allowPartialResults',
7069
'awaitData',
@@ -93,16 +92,10 @@ public static function configureOptions(OptionsResolver $resolver)
9392
->setAllowedTypes('batchSize', 'integer')
9493
->setAllowedTypes('collation', 'string')
9594
->setAllowedTypes('comment', 'string')
96-
->setAllowedValues('cursorType', [
97-
self::CURSOR_TYPE_NON_TAILABLE,
98-
self::CURSOR_TYPE_TAILABLE,
99-
self::CURSOR_TYPE_TAILABLE_AWAIT,
100-
])
10195
->setAllowedTypes('exhaust', 'bool')
10296
->setAllowedTypes('limit', 'integer')
10397
->setAllowedTypes('maxTimeMS', 'integer')
10498
->setAllowedTypes('modifiers', ['array', 'object'])
105-
->setDefault('modifiers', [])
10699
->setAllowedTypes('noCursorTimeout', 'bool')
107100
->setAllowedTypes('oplogReplay', 'bool')
108101
->setAllowedTypes('projection', ['array', 'object'])
@@ -111,5 +104,15 @@ public static function configureOptions(OptionsResolver $resolver)
111104
->setAllowedTypes('skip', 'integer')
112105
->setAllowedTypes('sort', ['array', 'object'])
113106
->setAllowedTypes('typeMap', 'array');
107+
108+
$resolver->setAllowedValues('cursorType', [
109+
self::CURSOR_TYPE_NON_TAILABLE,
110+
self::CURSOR_TYPE_TAILABLE,
111+
self::CURSOR_TYPE_TAILABLE_AWAIT,
112+
]);
113+
114+
$resolver
115+
->setDefault('modifiers', [])
116+
->setDefault('typeMap', TypeMapOptions::getDefaultTypeMap());
114117
}
115118
}

src/Options/DatabaseAndCollectionOptions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public static function configureOptions(OptionsResolver $resolver)
2727
->setAllowedTypes('writeConcern', WriteConcern::class)
2828
->setAllowedTypes('typeMap', 'array');
2929

30+
$resolver->setDefault('typeMap', TypeMapOptions::getDefaultTypeMap());
31+
3032
$resolver->setNormalizer('typeMap', function(Options $options, array $typeMap) {
3133
return TypeMapOptions::resolve($typeMap);
3234
});

src/Options/Traits/CachedResolverTrait.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ trait CachedResolverTrait
2020
public static function resolve(array $options)
2121
{
2222
try {
23-
return self::getResolver()->resolve($options);
23+
$options = self::getResolver()->resolve($options);
24+
25+
return array_filter($options, function($optionValue) {
26+
return null !== $optionValue; // ability to delete option by setting it to null
27+
});
2428
} catch (OptionsResolverException $e) {
2529
throw new InvalidArgumentException($e->getMessage());
2630
}

0 commit comments

Comments
 (0)