diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 2645e1505c..c4cb391ccc 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -403,6 +403,9 @@ public function loadByID($documentName, $id) { $collection = $this->getDocumentCollection($documentName); $result = $collection->findOne(array('_id' => new \MongoId($id))); + if ( ! $result) { + throw new \InvalidArgumentException(sprintf('Could not loadByID because ' . $documentName . ' '.$id . ' does not exist anymore.')); + } return $this->load($documentName, $id, $result); } diff --git a/lib/Doctrine/ODM/MongoDB/MongoCursor.php b/lib/Doctrine/ODM/MongoDB/MongoCursor.php index 789beb6186..a72dfcc2d7 100644 --- a/lib/Doctrine/ODM/MongoDB/MongoCursor.php +++ b/lib/Doctrine/ODM/MongoDB/MongoCursor.php @@ -35,12 +35,16 @@ class MongoCursor implements \Iterator { /** The DocumentManager instance. */ private $_dm; + /** The UnitOfWork instance. */ private $_uow; + /** The ClassMetadata instance. */ private $_class; + /** The PHP MongoCursor being wrapped */ private $_mongoCursor; + /** Whether or not to try and hydrate the returned data */ private $_hydrate = true; @@ -133,6 +137,19 @@ public function getResults() return iterator_to_array($this); } + /** + * Get the first single result from the cursor. + * + * @return object $document The single document. + */ + public function getSingleResult() + { + if ($results = $this->getResults()) { + return array_shift($results); + } + return null; + } + /** @proxy */ public function __call($method, $arguments) { diff --git a/lib/Doctrine/ODM/MongoDB/Query.php b/lib/Doctrine/ODM/MongoDB/Query.php index 4f854fec39..74f316136d 100644 --- a/lib/Doctrine/ODM/MongoDB/Query.php +++ b/lib/Doctrine/ODM/MongoDB/Query.php @@ -33,35 +33,59 @@ */ class Query { + const TYPE_FIND = 1; + const TYPE_UPDATE = 2; + const TYPE_REMOVE = 3; + /** The DocumentManager instance for this query */ private $_dm; + /** The Document class name being queried */ private $_className; + /** The ClassMetadata instance for the class being queried */ private $_class; + /** Array of fields to select */ private $_select = array(); + /** Array of criteria to query for */ private $_where = array(); + + /** Array to pass to MongoCollection::update() 2nd argument */ + private $_newObj = array(); + /** Array of sort options */ private $_sort = array(); + /** Limit number of records */ private $_limit = null; + /** Skip a specified number of records (offset) */ private $_skip = null; + /** Pass hints to the MongoCursor */ private $_hints = array(); + /** Pass immortal to cursor */ private $_immortal = false; + /** Pass snapshot to cursor */ private $_snapshot = false; + /** Pass slaveOkaye to cursor */ private $_slaveOkay = false; + /** Whether or not to try and hydrate the returned data */ private $_hydrate = true; + /** Map reduce information */ private $_mapReduce = array(); + /** The type of query */ + private $_type = self::TYPE_FIND; + + /** Refresh hint */ const HINT_REFRESH = 1; /** @@ -91,7 +115,8 @@ public function getDocumentManager() } /** - * Whether or not to try and hydrate the returned data + * Whether or not to hydrate the data into objects or to return the raw results + * from mongo. * * @param boolean $bool */ @@ -105,7 +130,7 @@ public function hydrate($bool) * Set slave okaye. * * @param bool $bool - * @return Query $this + * @return Query */ public function slaveOkay($bool = true) { @@ -116,8 +141,8 @@ public function slaveOkay($bool = true) /** * Set snapshot. * - * @param bool $bool - * @return Query $this + * @param bool $bool + * @return Query */ public function snapshot($bool = true) { @@ -128,8 +153,8 @@ public function snapshot($bool = true) /** * Set immortal. * - * @param bool $bool - * @return Query $this + * @param bool $bool + * @return Query */ public function immortal($bool = true) { @@ -141,7 +166,7 @@ public function immortal($bool = true) * Pass a hint to the MongoCursor * * @param string $keyPattern - * @return Query $this + * @return Query */ public function hint($keyPattern) { @@ -153,12 +178,60 @@ public function hint($keyPattern) * Set the Document class being queried. * * @param string $className The Document class being queried. - * @return Query $this + * @return Query */ public function from($className) { - $this->_className = $className; - $this->_class = $this->_dm->getClassMetadata($className); + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_FIND; + return $this; + } + + /** + * Proxy method to from() to match mongo naming. + * + * @param string $className + * @return Query + */ + public function find($className = null) + { + return $this->from($className); + } + + /** + * Sets the query as an update query for the given class name or changes + * the type for the current class. + * + * @param string $className + * @return Query + */ + public function update($className = null) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_UPDATE; + return $this; + } + + /** + * Sets the query as a remove query for the given class name or changes + * the type for the current class. + * + * @param string $className + * @return Query + */ + public function remove($className = null) + { + if ($className !== null) { + $this->_className = $className; + $this->_class = $this->_dm->getClassMetadata($className); + } + $this->_type = self::TYPE_REMOVE; return $this; } @@ -166,7 +239,7 @@ public function from($className) * The fields to select. * * @param string $fieldName - * @return Query $this + * @return Query */ public function select($fieldName = null) { @@ -178,7 +251,7 @@ public function select($fieldName = null) * Add a new field to select. * * @param string $fieldName - * @return Query $this + * @return Query */ public function addSelect($fieldName = null) { @@ -194,9 +267,9 @@ public function addSelect($fieldName = null) * * @param string $fieldName * @param string $value - * @return Query $this + * @return Query */ - public function where($fieldName, $value) + public function where($fieldName, $value = null) { $this->_where = array(); $this->addWhere($fieldName, $value); @@ -208,9 +281,9 @@ public function where($fieldName, $value) * * @param string $fieldName * @param string $value - * @return Query $this + * @return Query */ - public function addWhere($fieldName, $value) + public function addWhere($fieldName, $value = null) { if ($fieldName === $this->_class->identifier) { $fieldName = '_id'; @@ -225,7 +298,7 @@ public function addWhere($fieldName, $value) * * @param string $fieldName * @param mixed $values - * @return Query $this + * @return Query */ public function whereIn($fieldName, $values) { @@ -235,9 +308,9 @@ public function whereIn($fieldName, $values) /** * Add where not in criteria. * - * @param string $fieldName - * @param mixed $values - * @return Query $this + * @param string $fieldName + * @param mixed $values + * @return Query */ public function whereNotIn($fieldName, $values) { @@ -247,9 +320,9 @@ public function whereNotIn($fieldName, $values) /** * Add where not equal criteria. * - * @param string $fieldName - * @param string $value - * @return Query $this + * @param string $fieldName + * @param string $value + * @return Query */ public function whereNotEqual($fieldName, $value) { @@ -259,9 +332,9 @@ public function whereNotEqual($fieldName, $value) /** * Add where greater than criteria. * - * @param string $fieldName - * @param string $value - * @return Query $this + * @param string $fieldName + * @param string $value + * @return Query */ public function whereGt($fieldName, $value) { @@ -271,9 +344,9 @@ public function whereGt($fieldName, $value) /** * Add where greater than or equal to criteria. * - * @param string $fieldName - * @param string $value - * @return Query $this + * @param string $fieldName + * @param string $value + * @return Query */ public function whereGte($fieldName, $value) { @@ -283,9 +356,9 @@ public function whereGte($fieldName, $value) /** * Add where less than criteria. * - * @param string $fieldName - * @param string $value - * @return Query $this + * @param string $fieldName + * @param string $value + * @return Query */ public function whereLt($fieldName, $value) { @@ -295,9 +368,9 @@ public function whereLt($fieldName, $value) /** * Add where less than or equal to criteria. * - * @param string $fieldName - * @param string $value - * @return Query $this + * @param string $fieldName + * @param string $value + * @return Query */ public function whereLte($fieldName, $value) { @@ -307,10 +380,10 @@ public function whereLte($fieldName, $value) /** * Add where range criteria. * - * @param string $fieldName - * @param string $start - * @param string $end - * @return Query $this + * @param string $fieldName + * @param string $start + * @param string $end + * @return Query */ public function whereRange($fieldName, $start, $end) { @@ -320,9 +393,9 @@ public function whereRange($fieldName, $start, $end) /** * Add where size criteria. * - * @param string $fieldName - * @param string $size - * @return Query $this + * @param string $fieldName + * @param string $size + * @return Query */ public function whereSize($fieldName, $size) { @@ -332,9 +405,9 @@ public function whereSize($fieldName, $size) /** * Add where exists criteria. * - * @param string $fieldName - * @param string $bool - * @return Query $this + * @param string $fieldName + * @param string $bool + * @return Query */ public function whereExists($fieldName, $bool) { @@ -344,9 +417,9 @@ public function whereExists($fieldName, $bool) /** * Add where type criteria. * - * @param string $fieldName - * @param string $type - * @return Query $this + * @param string $fieldName + * @param string $type + * @return Query */ public function whereType($fieldName, $type) { @@ -356,9 +429,9 @@ public function whereType($fieldName, $type) /** * Add where all criteria. * - * @param string $fieldName - * @param mixed $values - * @return Query $this + * @param string $fieldName + * @param mixed $values + * @return Query */ public function whereAll($fieldName, $values) { @@ -368,9 +441,9 @@ public function whereAll($fieldName, $values) /** * Add where mod criteria. * - * @param string $fieldName - * @param string $mod - * @return Query $this + * @param string $fieldName + * @param string $mod + * @return Query */ public function whereMod($fieldName, $mod) { @@ -380,9 +453,9 @@ public function whereMod($fieldName, $mod) /** * Set sort and erase all old sorts. * - * @param string $fieldName - * @param string $order - * @return Query $this + * @param string $fieldName + * @param string $order + * @return Query */ public function sort($fieldName, $order) { @@ -394,9 +467,9 @@ public function sort($fieldName, $order) /** * Add a new sort order. * - * @param string $fieldName - * @param string $order - * @return Query $this + * @param string $fieldName + * @param string $order + * @return Query */ public function addSort($fieldName, $order) { @@ -407,8 +480,8 @@ public function addSort($fieldName, $order) /** * Set the Document limit for the MongoCursor * - * @param string $limit - * @return Query $this + * @param string $limit + * @return Query */ public function limit($limit) { @@ -419,8 +492,8 @@ public function limit($limit) /** * Set the number of Documents to skip for the MongoCursor * - * @param string $skip - * @return Query $this + * @param string $skip + * @return Query */ public function skip($skip) { @@ -432,8 +505,8 @@ public function skip($skip) * Specify a map reduce operation for this query. * * @param mixed $map - * @param mixed $reduce - * @param array $options + * @param mixed $reduce + * @param array $options * @return Query */ public function mapReduce($map, $reduce, array $options = array()) @@ -482,14 +555,182 @@ public function mapReduceOptions(array $options) return $this; } + /** + * Set field to value. + * + * @param string $name + * @param mixed $value + * @param boolean $atomic + * @return Query + */ + public function set($name, $value, $atomic = true) + { + if ($atomic === true) { + $this->_newObj['$set'][$name] = $value; + } else { + $this->_newObj[$name] = $value; + } + return $this; + } + + /** + * Set the $newObj array + * + * @param array $newObj + */ + public function setNewObj($newObj) + { + $this->_newObj = $newObj; + return $this; + } + + /** + * Increment field by the number value if field is present in the document, + * otherwise sets field to the number value. + * + * @param string $name + * @param integer $value + * @return Query + */ + public function inc($name, $value) + { + $this->_newObj['$inc'][$name] = $value; + return $this; + } + + /** + * Deletes a given field. + * + * @param string $field + * @return Query + */ + public function unsetField($field) + { + $this->_newObj['$unset'][$field] = 1; + return $this; + } + + /** + * Appends value to field, if field is an existing array, otherwise sets + * field to the array [value] if field is not present. If field is present + * but is not an array, an error condition is raised. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function push($field, $value) + { + $this->_newObj['$push'][$field] = $value; + return $this; + } + + /** + * Appends each value in valueArray to field, if field is an existing + * array, otherwise sets field to the array valueArray if field is not + * present. If field is present but is not an array, an error condition is + * raised. + * + * @param string $field + * @param array $valueArray + * @return Query + */ + public function pushAll($field, array $valueArray) + { + $this->_newObj['$pushAll'][$field] = $valueArray; + return $this; + } + + /** + * Adds value to the array only if its not in the array already. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function addToSet($field, $value) + { + $this->_newObj['$addToSet'][$field] = $value; + return $this; + } + + /** + * Adds values to the array only they are not in the array already. + * + * @param string $field + * @param array $values + * @return Query + */ + public function addManyToSet($field, array $values) + { + $this->_newObj['$addToSet'][$field]['$each'] = $values; + } + + /** + * Removes the last element in an array. + * + * @param string $field The field name + * @param string $firstOrLast First is 1 and last is -1. + * @return Query + */ + public function pop($field, $firstOrLast = 1) + { + $this->_newObj['$pop'][$field] = $firstOrLast; + return $this; + } + + /** + * Removes all occurrences of value from field, if field is an array. + * If field is present but is not an array, an error condition is raised. + * + * @param string $field + * @param mixed $value + * @return Query + */ + public function pull($field, $value) + { + $this->_newObj['$pull'][$field] = $value; + return $this; + } + + /** + * Removes all occurrences of each value in value_array from field, if + * field is an array. If field is present but is not an array, an error + * condition is raised. + * + * @param string $field + * @param array $valueArray + * @return Query + */ + public function pullAll($field, array $valueArray) + { + $this->_newObj['$pullAll'][$field] = $valueArray; + return $this; + } + /** * Execute the query and return an array of results * - * @return array $results The array of results for the query. + * @param array $options + * @return mixed $result The result of the query. */ - public function execute() + public function execute(array $options = array()) { - return $this->getCursor()->getResults(); + switch ($this->_type) { + case self::TYPE_FIND; + return $this->getCursor()->getResults(); + break; + + case self::TYPE_REMOVE; + return $this->_dm->getDocumentCollection($this->_className) + ->remove($this->_where, $options); + break; + + case self::TYPE_UPDATE; + return $this->_dm->getDocumentCollection($this->_className) + ->update($this->_where, $this->_newObj, $options); + break; + } } /** @@ -510,10 +751,7 @@ public function count($all = false) */ public function getSingleResult() { - if ($result = $this->execute()) { - return array_shift($result); - } - return null; + return $this->getCursor()->getSingleResult(); } /** @@ -523,6 +761,12 @@ public function getSingleResult() */ public function getCursor() { + if ($this->_type !== self::TYPE_FIND) { + throw new \InvalidArgumentException( + 'Cannot get cursor for an update or remove query. Use execute() method.' + ); + } + if (isset($this->_mapReduce['map']) && $this->_mapReduce['reduce']) { $cursor = $this->_dm->mapReduce($this->_className, $this->_mapReduce['map'], $this->_mapReduce['reduce'], $this->_where, isset($this->_mapReduce['options']) ? $this->_mapReduce['options'] : array()); $cursor->hydrate(false); diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index d49e241f8e..b99f1b352e 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -1456,7 +1456,7 @@ private function _doRefresh($document, array &$visited) $class = $this->_dm->getClassMetadata(get_class($document)); if ($this->getDocumentState($document) == self::STATE_MANAGED) { - $this->_dm->find($class->name, $this->_documentIdentifiers[$oid], true); + $this->_dm->loadByID($class->name, $this->_documentIdentifiers[$oid]); } else { throw new \InvalidArgumentException("Document is not MANAGED."); } diff --git a/tests/Documents/User.php b/tests/Documents/User.php index 39656e7231..d23d992cf1 100644 --- a/tests/Documents/User.php +++ b/tests/Documents/User.php @@ -39,6 +39,9 @@ class User extends BaseDocument /** @ReferenceOne(targetDocument="Account", cascade={"all"}) */ protected $account; + /** @Int */ + protected $hits = 0; + public function __construct() { $this->phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); @@ -130,4 +133,14 @@ public function addGroup(Group $group) { $this->groups[] = $group; } + + public function getHits() + { + return $this->hits; + } + + public function setHits($hits) + { + $this->hits = $hits; + } } \ No newline at end of file diff --git a/tests/QueryTest.php b/tests/QueryTest.php index 65a6b44fa6..aac0db586b 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -12,13 +12,18 @@ class QueryTest extends BaseTest { - public function testBasicQuery() + public function setUp() { - $user = new User(); - $user->setUsername('boo'); - $this->dm->persist($user); + parent::setUp(); + + $this->user = new User(); + $this->user->setUsername('boo'); + $this->dm->persist($this->user); $this->dm->flush(); + } + public function testFindQuery() + { $query = $this->dm->createQuery('Documents\User') ->where('$where', "function() { return this.username == 'boo' }"); $user = $query->getSingleResult(); @@ -28,6 +33,60 @@ public function testBasicQuery() ->reduce("function() { return this.username == 'boo' }"); $user = $query->getSingleResult(); $this->assertEquals('boo', $user->getUsername()); + } + + public function testUpdateQuery() + { + $query = $this->dm->createQuery('Documents\User') + ->update() + ->where('username', 'boo') + ->set('username', 'crap'); + $result = $query->execute(); + + $this->dm->refresh($this->user); + $this->assertEquals('crap', $this->user->getUsername()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRemoveQuery() + { + $query = $this->dm->createQuery('Documents\User') + ->remove() + ->where('username', 'boo'); + $result = $query->execute(); + + // should invoke exception because $this->user doesn't exist anymore + $this->dm->refresh($this->user); + } + + public function testIncUpdateQuery() + { + $query = $this->dm->createQuery('Documents\User') + ->update() + ->inc('hits', 5) + ->where('username', 'boo'); + $query->execute(); + $query->execute(); + + $user = $query->from('Documents\User') + ->hydrate(false) + ->getSingleResult(); + $this->assertEquals(10, $user['hits']); + } + + public function testUnsetFieldUpdateQuery() + { + $query = $this->dm->createQuery('Documents\User') + ->update() + ->unsetField('hits') + ->where('username', 'boo'); + $result = $query->execute(); + $user = $query->from('Documents\User') + ->hydrate(false) + ->getSingleResult(); + $this->assertFalse(isset($user['hits'])); } } \ No newline at end of file