You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

278 lines
8.0 KiB

<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongo;
use yii\base\InvalidParamException;
use yii\base\Object;
use Yii;
/**
* Collection represents the Mongo collection information.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @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
$result[$key]['$in'] = $actualValue;
} else {
$result[$key] = $actualValue;
}
}
}
return $result;
}
}