Commit 02e651cb authored by Jeremy Mikola's avatar Jeremy Mikola

Extract Collection findAndModify methods to operation classes

parent e36a304c
This diff is collapsed.
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Exception\UnexpectedValueException;
/**
* Operation for the findAndModify command.
*
* This class is used internally by the FindOneAndDelete, FindOneAndReplace, and
* FindOneAndUpdate operation classes.
*
* @internal
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindAndModify implements Executable
{
private $databaseName;
private $collectionName;
private $options;
/**
* Constructs a findAndModify command.
*
* Supported options:
*
* * fields (document): Limits the fields to return for the matching
* document.
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * new (boolean): When true, returns the modified document rather than
* the original. This option is ignored for remove operations. The
* The default is false.
*
* * query (document): Query by which to filter documents.
*
* * remove (boolean): When true, removes the matched document. This option
* cannot be true if the update option is set. The default is false.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * update (document): Update or replacement to apply to the matched
* document. This option cannot be set if the remove option is true.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. This option is ignored for remove operations. The
* default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, array $options)
{
$options += array(
'new' => false,
'remove' => false,
'upsert' => false,
);
if (isset($options['fields']) && ! is_array($options['fields']) && ! is_object($options['fields'])) {
throw new InvalidArgumentTypeException('"fields" option', $options['fields'], 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if ( ! is_bool($options['new'])) {
throw new InvalidArgumentTypeException('"new" option', $options['new'], 'boolean');
}
if (isset($options['query']) && ! is_array($options['query']) && ! is_object($options['query'])) {
throw new InvalidArgumentTypeException('"query" option', $options['query'], 'array or object');
}
if ( ! is_bool($options['remove'])) {
throw new InvalidArgumentTypeException('"remove" option', $options['remove'], 'boolean');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if (isset($options['update']) && ! is_array($options['update']) && ! is_object($options['update'])) {
throw new InvalidArgumentTypeException('"update" option', $options['update'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
if ( ! (isset($options['update']) xor $options['remove'])) {
throw new InvalidArgumentException('The "remove" option must be true or an "update" document must be specified, but not both');
}
$this->databaseName = (string) $databaseName;
$this->collectionName = (string) $collectionName;
$this->options = $options;
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|null
*/
public function execute(Server $server)
{
$cursor = $server->executeCommand($this->databaseName, $this->createCommand());
$result = current($cursor->toArray());
if (empty($result['ok'])) {
throw new RuntimeException(isset($result['errmsg']) ? $result['errmsg'] : 'Unknown error');
}
if ( ! isset($result['value'])) {
return null;
}
/* Prior to 3.0, findAndModify returns an empty document instead of null
* when an upsert is performed and the pre-modified document was
* requested.
*/
if ($this->options['upsert'] && ! $this->options['new'] &&
isset($result['lastErrorObject']->updatedExisting) &&
! $result['lastErrorObject']->updatedExisting) {
return null;
}
if ( ! is_object($result['value'])) {
throw new UnexpectedValueException('findAndModify command did not return a "value" document');
}
return (array) $result['value'];
}
/**
* Create the findAndModify command.
*
* @return Command
*/
private function createCommand()
{
$cmd = array(
'findAndModify' => $this->collectionName,
);
if ($this->options['remove']) {
$cmd['remove'] = true;
} else {
$cmd['new'] = $this->options['new'];
$cmd['upsert'] = $this->options['upsert'];
}
foreach (array('fields', 'query', 'sort', 'update') as $option) {
if (isset($this->options[$option])) {
$cmd[$option] = (object) $this->options[$option];
}
}
if (isset($this->options['maxTimeMS'])) {
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
}
return new Command($cmd);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for deleting a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndDelete()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndDelete implements Executable
{
private $findAndModify;
/**
* Constructs a findAndModify command for deleting a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'query' => $filter,
'remove' => true,
'sort' => isset($options['sort']) ? $options['sort'] : null,
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for replacing a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndReplace()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndReplace implements Executable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for replacing a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either RETURN_DOCUMENT_BEFORE or
* RETURN_DOCUMENT_AFTER. The default is RETURN_DOCUMENT_BEFORE.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $replacement Replacement document
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, $replacement, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if ( ! is_array($replacement) && ! is_object($replacement)) {
throw new InvalidArgumentTypeException('$replacement', $replacement, 'array or object');
}
if (\MongoDB\is_first_key_operator($replacement)) {
throw new InvalidArgumentException('First key in $replacement argument is an update operator');
}
$options += array(
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
);
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER,
'query' => $filter,
'sort' => isset($options['sort']) ? $options['sort'] : null,
'update' => $replacement,
'upsert' => $options['upsert'],
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
<?php
namespace MongoDB\Operation;
use MongoDB\Driver\Command;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\InvalidArgumentTypeException;
/**
* Operation for updating a document with the findAndModify command.
*
* @api
* @see MongoDB\Collection::findOneAndUpdate()
* @see http://docs.mongodb.org/manual/reference/command/findAndModify/
*/
class FindOneAndUpdate implements Executable
{
const RETURN_DOCUMENT_BEFORE = 1;
const RETURN_DOCUMENT_AFTER = 2;
private $findAndModify;
/**
* Constructs a findAndModify command for updating a document.
*
* Supported options:
*
* * maxTimeMS (integer): The maximum amount of time to allow the query to
* run.
*
* * projection (document): Limits the fields to return for the matching
* document.
*
* * returnDocument (enum): Whether to return the document before or after
* the update is applied. Must be either RETURN_DOCUMENT_BEFORE or
* RETURN_DOCUMENT_AFTER. The default is RETURN_DOCUMENT_BEFORE.
*
* * sort (document): Determines which document the operation modifies if
* the query selects multiple documents.
*
* * upsert (boolean): When true, a new document is created if no document
* matches the query. The default is false.
*
* @param string $databaseName Database name
* @param string $collectionName Collection name
* @param array|object $filter Query by which to filter documents
* @param array|object $update Update to apply to the matched document
* @param array $options Command options
* @throws InvalidArgumentException
*/
public function __construct($databaseName, $collectionName, $filter, $update, array $options = array())
{
if ( ! is_array($filter) && ! is_object($filter)) {
throw new InvalidArgumentTypeException('$filter', $filter, 'array or object');
}
if ( ! is_array($update) && ! is_object($update)) {
throw new InvalidArgumentTypeException('$update', $update, 'array or object');
}
if ( ! \MongoDB\is_first_key_operator($update)) {
throw new InvalidArgumentException('First key in $update argument is not an update operator');
}
$options += array(
'returnDocument' => self::RETURN_DOCUMENT_BEFORE,
'upsert' => false,
);
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
throw new InvalidArgumentTypeException('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
}
if (isset($options['projection']) && ! is_array($options['projection']) && ! is_object($options['projection'])) {
throw new InvalidArgumentTypeException('"projection" option', $options['projection'], 'array or object');
}
if ( ! is_integer($options['returnDocument'])) {
throw new InvalidArgumentTypeException('"returnDocument" option', $options['returnDocument'], 'integer');
}
if ($options['returnDocument'] !== self::RETURN_DOCUMENT_AFTER &&
$options['returnDocument'] !== self::RETURN_DOCUMENT_BEFORE) {
throw new InvalidArgumentException('Invalid value for "returnDocument" option: ' . $options['returnDocument']);
}
if (isset($options['sort']) && ! is_array($options['sort']) && ! is_object($options['sort'])) {
throw new InvalidArgumentTypeException('"sort" option', $options['sort'], 'array or object');
}
if ( ! is_bool($options['upsert'])) {
throw new InvalidArgumentTypeException('"upsert" option', $options['upsert'], 'boolean');
}
$this->findAndModify = new FindAndModify(
$databaseName,
$collectionName,
array(
'fields' => isset($options['projection']) ? $options['projection'] : null,
'maxTimeMS' => isset($options['maxTimeMS']) ? $options['maxTimeMS'] : null,
'new' => $options['returnDocument'] === self::RETURN_DOCUMENT_AFTER,
'query' => $filter,
'sort' => isset($options['sort']) ? $options['sort'] : null,
'update' => $update,
'upsert' => $options['upsert'],
)
);
}
/**
* Execute the operation.
*
* @see Executable::execute()
* @param Server $server
* @return array|null
*/
public function execute(Server $server)
{
return $this->findAndModify->execute($server);
}
}
......@@ -3,6 +3,7 @@
namespace MongoDB\Tests\Collection\CrudSpec;
use MongoDB\Collection;
use MongoDB\Operation\FindOneAndReplace;
/**
* CRUD spec functional tests for findOneAndReplace().
......@@ -46,7 +47,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
......@@ -89,7 +90,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
......@@ -156,7 +157,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndReplace($filter, $replacement, $options);
......@@ -179,7 +180,7 @@ class FindOneAndReplaceFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndReplace::RETURN_DOCUMENT_AFTER,
'upsert' => true,
);
......
......@@ -3,6 +3,7 @@
namespace MongoDB\Tests\Collection\CrudSpec;
use MongoDB\Collection;
use MongoDB\Operation\FindOneAndUpdate;
/**
* CRUD spec functional tests for findOneAndUpdate().
......@@ -46,7 +47,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
......@@ -89,7 +90,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
......@@ -155,7 +156,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
);
$document = $this->collection->findOneAndUpdate($filter, $update, $options);
......@@ -177,7 +178,7 @@ class FindOneAndUpdateFunctionalTest extends FunctionalTestCase
$options = array(
'projection' => array('x' => 1, '_id' => 0),
'sort' => array('x' => 1),
'returnDocument' => Collection::FIND_ONE_AND_RETURN_AFTER,
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
'upsert' => true,
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment