Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MongoDB Collation, Aggregation, and update/insertMany operations #1196

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/tmp/
/.idea/
/nbproject/
225 changes: 225 additions & 0 deletions lib/db/mongo/aggregation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<?php

namespace DB\Mongo;

/**
* Helper class to easily build aggregations pipeline arrays
*
* @see https://docs.mongodb.com/manual/aggregation/
* @see https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
* @see https://docs.mongodb.com/php-library/current/reference/method/MongoDBCollection-aggregate/
*/
class Aggregation
{

/**
* The aggregation pipeline array
* @var array
*/
protected $pipeline = [];

/**
* Add a $match step
*
* @example [field => value]
* @example [field => ['$gte' => value]]
* @example ['$or' => [[field => value], [field => ['$ne' => 1]]]
*
* @param mixed $filter The BSON filter to match against
*/
public function match($filter)
{
$this->pipeline[] = [
'$match' => $filter,
];

return $this;
}

/**
* Add a lookup step
*
* @param string $from
* @param string $localField
* @param string $foreignField
* @param string $as
*/
public function lookup(string $from, string $localField, string $foreignField, string $as)
{
$this->pipeline[] = [
'$lookup' => [
'from' => $from,
'localField' => $localField,
'foreignField' => $foreignField,
'as' => $as,
],
];

return $this;
}

/**
* Add a graphLookup step
*
* @param string $from
* @param string $startWith
* @param string $connectFrom
* @param string $connectTo
* @param string $as
* @param int $maxDepth
* @param stdClass $restrictWithin
*/
public function graphLookup(string $from, string $startWith, string $connectFrom, string $connectTo, string $as, int $maxDepth, $restrictWithin = null)
{
$this->pipeline[] = [
'$graphLookup' => [
'from' => $from,
'startWith' => $startWith,
'connectFromField' => $connectFrom,
'connectToField' => $connectTo,
'as' => $as,
'maxDepth' => $maxDepth,
'restrictSearchWithMatch' => $restrictWithin ?? new \stdClass(),
]
];

return $this;
}

/**
* Add an unwind step
*
* @param string $path
* @param bool $preserveEmpty
*/
public function unwind(string $path, bool $preserveEmpty = false)
{
$this->pipeline[] = [
'$unwind' => [
'path' => $path,
'preserveNullAndEmptyArrays' => $preserveEmpty,
]
];

return $this;
}

/**
* Add a projection step
*
* @param array $projection
*/
public function project(array $projection)
{
$this->pipeline[] = [
'$project' => $projection,
];

return $this;
}

/**
* Add an addFields step
*
* @param array $fields
* @return $this
*/
public function addFields(array $fields)
{
$this->pipeline[] = [
'$addFields' => $fields
];

return $this;
}

/**
* Add a count step
*
* @param string $name
* @return $this
*/
public function count(string $name)
{
$this->pipeline[] = [
'$count' => $name,
];

return $this;
}

/**
* Add a sorting step
*
* @param array $sortBy
* @return $this
*/
public function sort(array $sortBy)
{
$this->pipeline[] = [
'$sort' => $sortBy,
];

return $this;
}

/**
* Skip a number of documents before the next stage
*
* @param int $count
* @return $this
*/
public function skip(int $count)
{
$this->pipeline[] = [
'$skip' => $count,
];

return $this;
}

/**
* Filter aggregated array of data
*
* @param sting $input
* @param string $as
* @param array $condition
* @return $this
*/
public function filter(string $input, string $as, array $condition)
{
$this->pipeline[] = [
'input' => $input,
'as' => $as,
'cond' => $condition,
];

return $this;
}

/**
* Limit the number of documents passed to the next stage
*
* @param int $count
* @return $this
*/
public function limit(int $count)
{
$this->pipeline[] = [
'$limit' => $count,
];

return $this;
}

/**
* Get the resulting pipeline
*
* @return array
*/
public function getPipeline()
{
return $this->pipeline;
}

}
80 changes: 75 additions & 5 deletions lib/db/mongo/mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
'offset'=>0,
'collation'=>NULL,
];
$tag='';
if (is_array($ttl))
Expand Down Expand Up @@ -168,6 +169,8 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
$this->cursor=$this->cursor->limit($options['limit']);
if ($options['offset'])
$this->cursor=$this->cursor->skip($options['offset']);
if ($options['collation'])
$this->cursor=$this->cursor->collation($options['collation']);
$result=[];
while ($this->cursor->hasnext())
$result[]=$this->cursor->getnext();
Expand All @@ -176,7 +179,8 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
$this->cursor=$collection->find($filter,[
'sort'=>$options['order'],
'limit'=>$options['limit'],
'skip'=>$options['offset']
'skip'=>$options['offset'],
'collation'=>$options['collation'],
]);
$result=$this->cursor->toarray();
}
Expand Down Expand Up @@ -206,7 +210,8 @@ function find($filter=NULL,array $options=NULL,$ttl=0) {
'group'=>NULL,
'order'=>NULL,
'limit'=>0,
'offset'=>0
'offset'=>0,
'collation'=>NULL,
];
return $this->select($this->fields,$filter,$options,$ttl);
}
Expand All @@ -227,7 +232,7 @@ function count($filter=NULL,array $options=NULL,$ttl=0) {
if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
[$filter])).($tag?'.'.$tag:'').'.mongo',$result)) || !$ttl ||
$cached[0]+$ttl<microtime(TRUE)) {
$result=$this->collection->count($filter?:[]);
$result=$this->collection->count($filter?:[],$options);
if ($fw->CACHE && $ttl)
// Save to cache backend
$cache->set($hash,$result,$ttl);
Expand Down Expand Up @@ -298,8 +303,8 @@ function update() {
/**
* Delete current record
* @return bool
* @param $quick bool
* @param $filter array
* @param $quick bool
**/
function erase($filter=NULL,$quick=TRUE) {
if ($filter) {
Expand Down Expand Up @@ -328,6 +333,71 @@ function erase($filter=NULL,$quick=TRUE) {
return $result;
}

/**
* Run an aggregation pipeline on the collection
*
* @see Aggregate::__construct() for supported options
* @see https://docs.mongodb.com/php-library/current/reference/method/MongoDBCollection-aggregate/
*
* @param array $aggregation The aggregation pipeline
* @param array $options Command options
* @return array
*/
public function aggregate(aggregation $aggregation, array $options=[]) {
$result=$this->collection->aggregate($aggregation->getPipeline(), $options);

return $result->toarray();
}

/**
* Update multiple documents in one shot
*
* @see UpdateMany::__construct() for supported options
* @see https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/index.html
* @see https://docs.mongodb.com/php-library/v1.7/reference/write-result-classes/#phpclass.MongoDB\UpdateResult
*
* @param array $filter Query by which to filter documents
* @param array $update Update to apply to the matched documents
* @param array $options Command options
*
* @return UpdateResult
*/
public function updateMany(array $filter, array $update, array $options=[]) {
return $this->collection->updateMany($filter, $update, $options);
}

/**
* Inserts multiple documents.
*
* @see InsertMany::__construct() for supported options
* @see https://docs.mongodb.com/php-library/v1.7/reference/method/MongoDBCollection-insertMany/
* @see https://docs.mongodb.com/php-library/v1.7/reference/write-result-classes/#phpclass.MongoDB\InsertManyResult
*
* @param array[]|object[] $documents The documents to insert
* @param array $options Command options
* @return InsertManyResult
* @throws InvalidArgumentException for parameter/option parsing errors
* @throws DriverRuntimeException for other driver errors (e.g. connection errors)
*/
public function insertMany(array $documents, array $options=[]) {
return $this->collection->insertMany($documents, $options);
}

/**
* Finds documents and can modify at the same time
*
* @see findOneAndUpdate::__construct() for supported options
* @see https://docs.mongodb.com/php-library/v1.7/reference/method/MongoDBCollection-findOneAndUpdate/
*
* @param array $filter
* @param array $update
* @param array $options
* @return array or null
*/
public function findOneAndUpdate(array $filter, array $update=[], array $options=[]) {
return $this->collection->findOneAndUpdate($filter, $update, $options);
}

/**
* Reset cursor
* @return NULL
Expand Down