* @since 2.0 */ class Collection extends Object { /** * @var \MongoCollection Mongo collection instance. */ public $mongoCollection; /** * Drops this collection. */ public function drop() { $this->mongoCollection->drop(); } /** * @param array $condition * @param array $fields * @return \MongoCursor */ public function find($condition = [], $fields = []) { return $this->mongoCollection->find($this->buildCondition($condition), $fields); } /** * @param array $condition * @param array $fields * @return array */ public function findAll($condition = [], $fields = []) { $cursor = $this->find($condition, $fields); $result = []; foreach ($cursor as $data) { $result[] = $data; } return $result; } /** * Inserts new data into collection. * @param array|object $data data to be inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoId new record id instance. * @throws Exception on failure. */ public function insert($data, $options = []) { $token = 'Inserting data into ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); $options = array_merge(['w' => 1], $options); $this->tryResultError($this->mongoCollection->insert($data, $options)); Yii::endProfile($token, __METHOD__); return is_array($data) ? $data['_id'] : $data->_id; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); throw new Exception($e->getMessage(), (int)$e->getCode(), $e); } } /** * Inserts several new rows into collection. * @param array $rows array of arrays or objects to be inserted. * @param array $options list of options in format: optionName => optionValue. * @return array inserted data, each row will have "_id" key assigned to it. * @throws Exception on failure. */ public function batchInsert($rows, $options = []) { $token = 'Inserting batch data into ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); $options = array_merge(['w' => 1], $options); $this->tryResultError($this->mongoCollection->batchInsert($rows, $options)); Yii::endProfile($token, __METHOD__); return $rows; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); throw new Exception($e->getMessage(), (int)$e->getCode(), $e); } } /** * Updates the rows, which matches given criteria by given data. * @param array $condition description of the objects to update. * @param array $newData the object with which to update the matching records. * @param array $options list of options in format: optionName => optionValue. * @return integer|boolean number of updated documents or whether operation was successful. * @throws Exception on failure. */ public function update($condition, $newData, $options = []) { $token = 'Updating data in ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); $options = array_merge(['w' => 1, 'multiple' => true], $options); if ($options['multiple']) { $keys = array_keys($newData); if (!empty($keys) && strncmp('$', $keys[0], 1) !== 0) { $newData = ['$set' => $newData]; } } $condition = $this->buildCondition($condition); $result = $this->mongoCollection->update($condition, $newData, $options); $this->tryResultError($result); Yii::endProfile($token, __METHOD__); if (is_array($result) && array_key_exists('n', $result)) { return $result['n']; } else { return true; } } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); throw new Exception($e->getMessage(), (int)$e->getCode(), $e); } } /** * Update the existing database data, otherwise insert this data * @param array|object $data data to be updated/inserted. * @param array $options list of options in format: optionName => optionValue. * @return \MongoId updated/new record id instance. * @throws Exception on failure. */ public function save($data, $options = []) { $token = 'Saving data into ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); $options = array_merge(['w' => 1], $options); $this->tryResultError($this->mongoCollection->save($data, $options)); Yii::endProfile($token, __METHOD__); return is_array($data) ? $data['_id'] : $data->_id; } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); throw new Exception($e->getMessage(), (int)$e->getCode(), $e); } } /** * Removes data from the collection. * @param array $condition description of records to remove. * @param array $options list of options in format: optionName => optionValue. * @return integer|boolean number of updated documents or whether operation was successful. * @throws Exception on failure. */ public function remove($condition = [], $options = []) { $token = 'Removing data from ' . $this->mongoCollection->getName(); Yii::info($token, __METHOD__); try { Yii::beginProfile($token, __METHOD__); $options = array_merge(['w' => 1, 'multiple' => true], $options); $result = $this->mongoCollection->remove($this->buildCondition($condition), $options); $this->tryResultError($result); Yii::endProfile($token, __METHOD__); if (is_array($result) && array_key_exists('n', $result)) { return $result['n']; } else { return true; } } catch (\Exception $e) { Yii::endProfile($token, __METHOD__); throw new Exception($e->getMessage(), (int)$e->getCode(), $e); } } /** * Checks if command execution result ended with an error. * @param mixed $result raw command execution result. * @throws Exception if an error occurred. */ protected function tryResultError($result) { if (is_array($result)) { if (!empty($result['err'])) { throw new Exception($result['errmsg'], (int)$result['code']); } } elseif (!$result) { throw new Exception('Unknown error, use "w=1" option to enable error tracking'); } } /** * Converts user friendly condition keyword into actual Mongo condition keyword. * @param string $key raw condition key. * @return string actual key. */ protected function normalizeConditionKeyword($key) { static $map = [ 'or' => '$or', '>' => '$gt', '>=' => '$gte', '<' => '$lt', '<=' => '$lte', '!=' => '$ne', '<>' => '$ne', 'in' => '$in', 'not in' => '$nin', 'all' => '$all', 'size' => '$size', 'type' => '$type', 'exists' => '$exists', 'notexists' => '$exists', 'elemmatch' => '$elemMatch', 'mod' => '$mod', '%' => '$mod', '=' => '$$eq', '==' => '$$eq', 'where' => '$where' ]; $key = strtolower($key); if (array_key_exists($key, $map)) { return $map[$key]; } else { return $key; } } /** * Builds up Mongo condition from user friendly condition. * @param array $condition raw condition. * @return array normalized Mongo condition. * @throws \yii\base\InvalidParamException on invalid condition given. */ public function buildCondition($condition) { if (!is_array($condition)) { throw new InvalidParamException('Condition should be an array.'); } $result = []; foreach ($condition as $key => $value) { if (is_array($value)) { $actualValue = $this->buildCondition($value); } else { $actualValue = $value; } if (is_numeric($key)) { $result[] = $actualValue; } else { $key = $this->normalizeConditionKeyword($key); if (strncmp('$', $key, 1) !== 0 && is_array($actualValue) && array_key_exists(0, $actualValue)) { // shortcut for IN condition if ($key == '_id') { foreach ($actualValue as &$actualValuePart) { if (!is_object($actualValuePart)) { $actualValuePart = new \MongoId($actualValuePart); } } } $result[$key]['$in'] = $actualValue; } else { if ($key == '_id' && !is_object($actualValue)) { $actualValue = new \MongoId($actualValue); } $result[$key] = $actualValue; } } } return $result; } }