Skip to content

Commit 76d62e2

Browse files
committed
Merge pull request #4
2 parents f3492fb + 8e89680 commit 76d62e2

29 files changed

+1532
-180
lines changed

.travis.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,29 @@ php:
77
- 5.6
88

99
env:
10-
- MONGODB_VERSION=alpha
10+
global:
11+
- KEY_SERVER="hkp://keyserver.ubuntu.com:80"
12+
- MONGO_REPO_URI="http://repo.mongodb.com/apt/ubuntu"
13+
- MONGO_REPO_TYPE="precise/mongodb-enterprise/"
14+
- SOURCES_LOC="/etc/apt/sources.list.d/mongodb.list"
15+
matrix:
16+
- DRIVER_VERSION=alpha SERVER_VERSION=2.4
17+
- DRIVER_VERSION=alpha SERVER_VERSION=2.6
18+
- DRIVER_VERSION=alpha SERVER_VERSION=3.0
1119

12-
services: mongodb
20+
before_install:
21+
- sudo apt-key adv --keyserver ${KEY_SERVER} --recv 7F0CEB10
22+
- echo "deb ${MONGO_REPO_URI} ${MONGO_REPO_TYPE}${SERVER_VERSION} multiverse" | sudo tee ${SOURCES_LOC}
23+
- sudo apt-get update -qq
24+
25+
install:
26+
- if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_PACKAGE=mongodb-10gen-enterprise; else export SERVER_PACKAGE=mongodb-enterprise; fi
27+
- sudo apt-get install ${SERVER_PACKAGE}
1328

1429
before_script:
15-
- mongo --eval 'tojson(db.runCommand({buildInfo:1}))'
16-
- pecl install -f mongodb-${MONGODB_VERSION}
30+
- if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_SERVICE=mongodb; else export SERVER_SERVICE=mongod; fi
31+
- if ! nc -z localhost 27017; then sudo service ${SERVER_SERVICE} start; fi
32+
- mongod --version
33+
- pecl install -f mongodb-${DRIVER_VERSION}
1734
- php --ri mongodb
1835
- composer install --dev --no-interaction --prefer-source

phpunit.xml.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<php>
1818
<ini name="error_reporting" value="-1"/>
19-
<env name="MONGODB_URI" value="mongodb://127.0.0.1:27017"/>
19+
<env name="MONGODB_URI" value="mongodb://127.0.0.1:27017/?serverSelectionTimeoutMS=100"/>
2020
<env name="MONGODB_DATABASE" value="phplib_test"/>
2121
</php>
2222

src/Collection.php

Lines changed: 124 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -71,37 +71,75 @@ public function __construct(Manager $manager, $namespace, WriteConcern $writeCon
7171

7272
/**
7373
* Runs an aggregation framework pipeline
74-
* NOTE: The return value of this method depends on your MongoDB server version
75-
* and possibly options.
76-
* MongoDB 2.6 (and later) will return a Cursor by default
77-
* MongoDB pre 2.6 will return an ArrayIterator
74+
*
75+
* Note: this method's return value depends on the MongoDB server version
76+
* and the "useCursor" option. If "useCursor" is true, a Cursor will be
77+
* returned; otherwise, an ArrayIterator is returned, which wraps the
78+
* "result" array from the command response document.
7879
*
7980
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
80-
* @see Collection::getAggregateOptions() for supported $options
8181
*
8282
* @param array $pipeline The pipeline to execute
8383
* @param array $options Additional options
8484
* @return Iterator
8585
*/
8686
public function aggregate(array $pipeline, array $options = array())
8787
{
88-
$options = array_merge($this->getAggregateOptions(), $options);
89-
$options = $this->_massageAggregateOptions($options);
90-
$cmd = array(
91-
"aggregate" => $this->collname,
92-
"pipeline" => $pipeline,
93-
) + $options;
88+
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
89+
$server = $this->manager->selectServer($readPreference);
90+
91+
if (FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) {
92+
$options = array_merge(
93+
array(
94+
/**
95+
* Enables writing to temporary files. When set to true, aggregation stages
96+
* can write data to the _tmp subdirectory in the dbPath directory. The
97+
* default is false.
98+
*
99+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
100+
*/
101+
'allowDiskUse' => false,
102+
/**
103+
* The number of documents to return per batch.
104+
*
105+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
106+
*/
107+
'batchSize' => 0,
108+
/**
109+
* The maximum amount of time to allow the query to run.
110+
*
111+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
112+
*/
113+
'maxTimeMS' => 0,
114+
/**
115+
* Indicates if the results should be provided as a cursor.
116+
*
117+
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
118+
*/
119+
'useCursor' => true,
120+
),
121+
$options
122+
);
123+
}
94124

95-
$cursor = $this->_runCommand($this->dbname, $cmd);
125+
$options = $this->_massageAggregateOptions($options);
126+
$command = new Command(array(
127+
'aggregate' => $this->collname,
128+
'pipeline' => $pipeline,
129+
) + $options);
130+
$cursor = $server->executeCommand($this->dbname, $command);
96131

97-
if (isset($cmd["cursor"]) && $cmd["cursor"]) {
132+
if ( ! empty($options["cursor"])) {
98133
return $cursor;
99134
}
100135

101136
$doc = current($cursor->toArray());
102137

103138
if ($doc["ok"]) {
104-
return new \ArrayIterator($doc["result"]);
139+
return new \ArrayIterator(array_map(
140+
function (\stdClass $document) { return (array) $document; },
141+
$doc["result"]
142+
));
105143
}
106144

107145
throw $this->_generateCommandException($doc);
@@ -239,12 +277,12 @@ public function count(array $filter = array(), array $options = array())
239277
{
240278
$cmd = array(
241279
"count" => $this->collname,
242-
"query" => $filter,
280+
"query" => (object) $filter,
243281
) + $options;
244282

245283
$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
246284
if ($doc["ok"]) {
247-
return $doc["n"];
285+
return (integer) $doc["n"];
248286
}
249287
throw $this->_generateCommandException($doc);
250288
}
@@ -363,7 +401,7 @@ public function distinct($fieldName, array $filter = array(), array $options = a
363401
$cmd = array(
364402
"distinct" => $this->collname,
365403
"key" => $fieldName,
366-
"query" => $filter,
404+
"query" => (object) $filter,
367405
) + $options;
368406

369407
$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
@@ -497,7 +535,7 @@ public function findOneAndDelete(array $filter, array $options = array())
497535

498536
$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
499537
if ($doc["ok"]) {
500-
return $doc["value"];
538+
return is_object($doc["value"]) ? (array) $doc["value"] : $doc["value"];
501539
}
502540

503541
throw $this->_generateCommandException($doc);
@@ -534,7 +572,7 @@ public function findOneAndReplace(array $filter, array $replacement, array $opti
534572

535573
$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
536574
if ($doc["ok"]) {
537-
return $doc["value"];
575+
return $this->_massageFindAndModifyResult($doc, $options);
538576
}
539577

540578
throw $this->_generateCommandException($doc);
@@ -572,61 +610,12 @@ public function findOneAndUpdate(array $filter, array $update, array $options =
572610

573611
$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
574612
if ($doc["ok"]) {
575-
return $doc["value"];
613+
return $this->_massageFindAndModifyResult($doc, $options);
576614
}
577615

578616
throw $this->_generateCommandException($doc);
579617
}
580618

581-
/**
582-
* Retrieves all aggregate options with their default values.
583-
*
584-
* @return array of Collection::aggregate() options
585-
*/
586-
public function getAggregateOptions()
587-
{
588-
$opts = array(
589-
/**
590-
* Enables writing to temporary files. When set to true, aggregation stages
591-
* can write data to the _tmp subdirectory in the dbPath directory. The
592-
* default is false.
593-
*
594-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
595-
*/
596-
"allowDiskUse" => false,
597-
598-
/**
599-
* The number of documents to return per batch.
600-
*
601-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
602-
*/
603-
"batchSize" => 0,
604-
605-
/**
606-
* The maximum amount of time to allow the query to run.
607-
*
608-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
609-
*/
610-
"maxTimeMS" => 0,
611-
612-
/**
613-
* Indicates if the results should be provided as a cursor.
614-
*
615-
* The default for this value depends on the version of the server.
616-
* - Servers >= 2.6 will use a default of true.
617-
* - Servers < 2.6 will use a default of false.
618-
*
619-
* As with any other property, this value can be changed.
620-
*
621-
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
622-
*/
623-
"useCursor" => true,
624-
);
625-
626-
/* FIXME: Add a version check for useCursor */
627-
return $opts;
628-
}
629-
630619
/**
631620
* Retrieves all Bulk Write options with their default values.
632621
*
@@ -961,7 +950,7 @@ public function getWriteOptions()
961950
*
962951
* @see http://docs.mongodb.org/manual/reference/command/insert/
963952
*
964-
* @param array $documents The documents to insert
953+
* @param array[]|object[] $documents The documents to insert
965954
* @return InsertManyResult
966955
*/
967956
public function insertMany(array $documents)
@@ -976,6 +965,8 @@ public function insertMany(array $documents)
976965

977966
if ($insertedId !== null) {
978967
$insertedIds[$i] = $insertedId;
968+
} else {
969+
$insertedIds[$i] = is_array($document) ? $document['_id'] : $document->_id;
979970
}
980971
}
981972

@@ -989,18 +980,21 @@ public function insertMany(array $documents)
989980
*
990981
* @see http://docs.mongodb.org/manual/reference/command/insert/
991982
*
992-
* @param array $document The document to insert
993-
* @param array $options Additional options
983+
* @param array|object $document The document to insert
994984
* @return InsertOneResult
995985
*/
996-
public function insertOne(array $document)
986+
public function insertOne($document)
997987
{
998988
$options = array_merge($this->getWriteOptions());
999989

1000990
$bulk = new BulkWrite($options["ordered"]);
1001991
$id = $bulk->insert($document);
1002992
$wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);
1003993

994+
if ($id === null) {
995+
$id = is_array($document) ? $document['_id'] : $document->_id;
996+
}
997+
1004998
return new InsertOneResult($wr, $id);
1005999
}
10061000

@@ -1038,7 +1032,7 @@ public function replaceOne(array $filter, array $update, array $options = array(
10381032
if (isset($firstKey[0]) && $firstKey[0] == '$') {
10391033
throw new InvalidArgumentException("First key in \$update must NOT be a \$operator");
10401034
}
1041-
$wr = $this->_update($filter, $update, $options);
1035+
$wr = $this->_update($filter, $update, $options + array("multi" => false));
10421036

10431037
return new UpdateResult($wr);
10441038
}
@@ -1057,7 +1051,7 @@ public function replaceOne(array $filter, array $update, array $options = array(
10571051
*/
10581052
public function updateMany(array $filter, $update, array $options = array())
10591053
{
1060-
$wr = $this->_update($filter, $update, $options + array("limit" => 0));
1054+
$wr = $this->_update($filter, $update, $options + array("multi" => true));
10611055

10621056
return new UpdateResult($wr);
10631057
}
@@ -1080,7 +1074,7 @@ public function updateOne(array $filter, array $update, array $options = array()
10801074
if (!isset($firstKey[0]) || $firstKey[0] != '$') {
10811075
throw new InvalidArgumentException("First key in \$update must be a \$operator");
10821076
}
1083-
$wr = $this->_update($filter, $update, $options);
1077+
$wr = $this->_update($filter, $update, $options + array("multi" => false));
10841078

10851079
return new UpdateResult($wr);
10861080
}
@@ -1146,14 +1140,66 @@ final protected function _generateCommandException($doc)
11461140
*/
11471141
protected function _massageAggregateOptions($options)
11481142
{
1149-
if ($options["useCursor"]) {
1150-
$options["cursor"] = array("batchSize" => $options["batchSize"]);
1143+
if ( ! empty($options["useCursor"])) {
1144+
$options["cursor"] = isset($options["batchSize"])
1145+
? array("batchSize" => (integer) $options["batchSize"])
1146+
: new stdClass;
11511147
}
11521148
unset($options["useCursor"], $options["batchSize"]);
11531149

11541150
return $options;
11551151
}
11561152

1153+
/**
1154+
* Internal helper for massaging findandmodify options
1155+
* @internal
1156+
*/
1157+
final protected function _massageFindAndModifyOptions($options, $update = array())
1158+
{
1159+
$ret = array(
1160+
"sort" => $options["sort"],
1161+
"new" => isset($options["returnDocument"]) ? $options["returnDocument"] == self::FIND_ONE_AND_RETURN_AFTER : false,
1162+
"fields" => $options["projection"],
1163+
"upsert" => isset($options["upsert"]) ? $options["upsert"] : false,
1164+
);
1165+
if ($update) {
1166+
$ret["update"] = $update;
1167+
} else {
1168+
$ret["remove"] = true;
1169+
}
1170+
return $ret;
1171+
}
1172+
1173+
/**
1174+
* Internal helper for massaging the findAndModify result.
1175+
*
1176+
* @internal
1177+
* @param array $result
1178+
* @param array $options
1179+
* @return array|null
1180+
*/
1181+
final protected function _massageFindAndModifyResult(array $result, array $options)
1182+
{
1183+
if ($result['value'] === null) {
1184+
return null;
1185+
}
1186+
1187+
/* Prior to 3.0, findAndModify returns an empty document instead of null
1188+
* when an upsert is performed and the pre-modified document was
1189+
* requested.
1190+
*/
1191+
if ($options['upsert'] && ! $options['new'] &&
1192+
isset($result['lastErrorObject']->updatedExisting) &&
1193+
! $result['lastErrorObject']->updatedExisting) {
1194+
1195+
return null;
1196+
}
1197+
1198+
return is_object($result["value"])
1199+
? (array) $result['value']
1200+
: $result['value'];
1201+
}
1202+
11571203
/**
11581204
* Constructs the Query Wire Protocol field 'flags' based on $options
11591205
* provided to other helpers

src/FeatureDetection.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class FeatureDetection
1414
const API_LISTCOLLECTIONS_CMD = 3;
1515
const API_LISTINDEXES_CMD = 3;
1616
const API_CREATEINDEXES_CMD = 2;
17+
const API_AGGREGATE_CURSOR = 2;
1718

1819
/**
1920
* Return whether the server supports a particular feature.

0 commit comments

Comments
 (0)