Qiang Xue
11 years ago
35 changed files with 4997 additions and 1 deletions
@ -0,0 +1,107 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo; |
||||
|
||||
use yii\db\ActiveQueryInterface; |
||||
use yii\db\ActiveQueryTrait; |
||||
|
||||
/** |
||||
* ActiveQuery represents a Mongo query associated with an Active Record class. |
||||
* |
||||
* ActiveQuery instances are usually created by [[ActiveRecord::find()]]. |
||||
* |
||||
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], |
||||
* [[orderBy()]] to customize the query options. |
||||
* |
||||
* ActiveQuery also provides the following additional query options: |
||||
* |
||||
* - [[with()]]: list of relations that this query should be performed with. |
||||
* - [[asArray()]]: whether to return each record as an array. |
||||
* |
||||
* These options can be configured using methods of the same name. For example: |
||||
* |
||||
* ~~~ |
||||
* $customers = Customer::find()->with('orders')->asArray()->all(); |
||||
* ~~~ |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class ActiveQuery extends Query implements ActiveQueryInterface |
||||
{ |
||||
use ActiveQueryTrait; |
||||
|
||||
/** |
||||
* Executes query and returns all results as an array. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If null, the Mongo connection returned by [[modelClass]] will be used. |
||||
* @return array the query results. If the query results in nothing, an empty array will be returned. |
||||
*/ |
||||
public function all($db = null) |
||||
{ |
||||
$cursor = $this->buildCursor($db); |
||||
$rows = $this->fetchRows($cursor); |
||||
if (!empty($rows)) { |
||||
$models = $this->createModels($rows); |
||||
if (!empty($this->with)) { |
||||
$this->findWith($this->with, $models); |
||||
} |
||||
return $models; |
||||
} else { |
||||
return []; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Executes query and returns a single row of result. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If null, the Mongo connection returned by [[modelClass]] will be used. |
||||
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], |
||||
* the query result may be either an array or an ActiveRecord object. Null will be returned |
||||
* if the query results in nothing. |
||||
*/ |
||||
public function one($db = null) |
||||
{ |
||||
$row = parent::one($db); |
||||
if ($row !== false) { |
||||
if ($this->asArray) { |
||||
$model = $row; |
||||
} else { |
||||
/** @var ActiveRecord $class */ |
||||
$class = $this->modelClass; |
||||
$model = $class::create($row); |
||||
} |
||||
if (!empty($this->with)) { |
||||
$models = [$model]; |
||||
$this->findWith($this->with, $models); |
||||
$model = $models[0]; |
||||
} |
||||
return $model; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo collection for this query. |
||||
* @param Connection $db Mongo connection. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public function getCollection($db = null) |
||||
{ |
||||
/** @var ActiveRecord $modelClass */ |
||||
$modelClass = $this->modelClass; |
||||
if ($db === null) { |
||||
$db = $modelClass::getDb(); |
||||
} |
||||
if ($this->from === null) { |
||||
$this->from = $modelClass::collectionName(); |
||||
} |
||||
return $db->getCollection($this->from); |
||||
} |
||||
} |
@ -0,0 +1,353 @@
|
||||
<?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\InvalidConfigException; |
||||
use yii\base\InvalidParamException; |
||||
use yii\db\BaseActiveRecord; |
||||
use yii\base\UnknownMethodException; |
||||
use yii\db\StaleObjectException; |
||||
use yii\helpers\Inflector; |
||||
use yii\helpers\StringHelper; |
||||
|
||||
/** |
||||
* ActiveRecord is the base class for classes representing Mongo documents in terms of objects. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
abstract class ActiveRecord extends BaseActiveRecord |
||||
{ |
||||
/** |
||||
* Returns the Mongo connection used by this AR class. |
||||
* By default, the "mongo" application component is used as the Mongo connection. |
||||
* You may override this method if you want to use a different database connection. |
||||
* @return Connection the database connection used by this AR class. |
||||
*/ |
||||
public static function getDb() |
||||
{ |
||||
return \Yii::$app->getComponent('mongo'); |
||||
} |
||||
|
||||
/** |
||||
* Updates all documents in the collection using the provided attribute values and conditions. |
||||
* For example, to change the status to be 1 for all customers whose status is 2: |
||||
* |
||||
* ~~~ |
||||
* Customer::updateAll(['status' => 1], ['status' = 2]); |
||||
* ~~~ |
||||
* |
||||
* @param array $attributes attribute values (name-value pairs) to be saved into the collection |
||||
* @param array $condition description of the objects to update. |
||||
* Please refer to [[Query::where()]] on how to specify this parameter. |
||||
* @param array $options list of options in format: optionName => optionValue. |
||||
* @return integer the number of documents updated. |
||||
*/ |
||||
public static function updateAll($attributes, $condition = [], $options = []) |
||||
{ |
||||
return static::getCollection()->update($condition, $attributes, $options); |
||||
} |
||||
|
||||
/** |
||||
* Updates all documents in the collection using the provided counter changes and conditions. |
||||
* For example, to increment all customers' age by 1, |
||||
* |
||||
* ~~~ |
||||
* Customer::updateAllCounters(['age' => 1]); |
||||
* ~~~ |
||||
* |
||||
* @param array $counters the counters to be updated (attribute name => increment value). |
||||
* Use negative values if you want to decrement the counters. |
||||
* @param array $condition description of the objects to update. |
||||
* Please refer to [[Query::where()]] on how to specify this parameter. |
||||
* @param array $options list of options in format: optionName => optionValue. |
||||
* @return integer the number of documents updated. |
||||
*/ |
||||
public static function updateAllCounters($counters, $condition = [], $options = []) |
||||
{ |
||||
return static::getCollection()->update($condition, ['$inc' => $counters], $options); |
||||
} |
||||
|
||||
/** |
||||
* Deletes documents in the collection using the provided conditions. |
||||
* WARNING: If you do not specify any condition, this method will delete documents rows in the collection. |
||||
* |
||||
* For example, to delete all customers whose status is 3: |
||||
* |
||||
* ~~~ |
||||
* Customer::deleteAll('status = 3'); |
||||
* ~~~ |
||||
* |
||||
* @param array $condition description of the objects to delete. |
||||
* Please refer to [[Query::where()]] on how to specify this parameter. |
||||
* @param array $options list of options in format: optionName => optionValue. |
||||
* @return integer the number of documents deleted. |
||||
*/ |
||||
public static function deleteAll($condition = [], $options = []) |
||||
{ |
||||
$options['w'] = 1; |
||||
if (!array_key_exists('multiple', $options)) { |
||||
$options['multiple'] = true; |
||||
} |
||||
return static::getCollection()->remove($condition, $options); |
||||
} |
||||
|
||||
/** |
||||
* Creates an [[ActiveQuery]] instance. |
||||
* This method is called by [[find()]] to start a "find" command. |
||||
* You may override this method to return a customized query (e.g. `CustomerQuery` specified |
||||
* written for querying `Customer` purpose.) |
||||
* @return ActiveQuery the newly created [[ActiveQuery]] instance. |
||||
*/ |
||||
public static function createQuery() |
||||
{ |
||||
return new ActiveQuery(['modelClass' => get_called_class()]); |
||||
} |
||||
|
||||
/** |
||||
* Declares the name of the Mongo collection associated with this AR class. |
||||
* Collection name can be either a string or array: |
||||
* - if string considered as the name of the collection inside the default database. |
||||
* - if array - first element considered as the name of the database, second - as |
||||
* name of collection inside that database |
||||
* By default this method returns the class name as the collection name by calling [[Inflector::camel2id()]]. |
||||
* For example, 'Customer' becomes 'customer', and 'OrderItem' becomes |
||||
* 'order_item'. You may override this method if the table is not named after this convention. |
||||
* @return string|array the collection name |
||||
*/ |
||||
public static function collectionName() |
||||
{ |
||||
return Inflector::camel2id(StringHelper::basename(get_called_class()), '_'); |
||||
} |
||||
|
||||
/** |
||||
* Return the Mongo collection instance for this AR class. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public static function getCollection() |
||||
{ |
||||
return static::getDb()->getCollection(static::collectionName()); |
||||
} |
||||
|
||||
/** |
||||
* Returns the primary key name(s) for this AR class. |
||||
* The default implementation will return ['_id']. |
||||
* |
||||
* Note that an array should be returned even for a collection with single primary key. |
||||
* |
||||
* @return string[] the primary keys of the associated Mongo collection. |
||||
*/ |
||||
public static function primaryKey() |
||||
{ |
||||
return ['_id']; |
||||
} |
||||
|
||||
/** |
||||
* Creates an [[ActiveRelation]] instance. |
||||
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance. |
||||
* You may override this method to return a customized relation. |
||||
* @param array $config the configuration passed to the ActiveRelation class. |
||||
* @return ActiveRelation the newly created [[ActiveRelation]] instance. |
||||
*/ |
||||
public static function createActiveRelation($config = []) |
||||
{ |
||||
return new ActiveRelation($config); |
||||
} |
||||
|
||||
/** |
||||
* Returns the list of all attribute names of the model. |
||||
* This method must be overridden by child classes to define available attributes. |
||||
* Note: primary key attribute "_id" should be always present in returned array. |
||||
* For example: |
||||
* ~~~ |
||||
* public function attributes() |
||||
* { |
||||
* return ['_id', 'name', 'address', 'status']; |
||||
* } |
||||
* ~~~ |
||||
* @return array list of attribute names. |
||||
*/ |
||||
public function attributes() |
||||
{ |
||||
throw new InvalidConfigException('The attributes() method of mongo ActiveRecord has to be implemented by child classes.'); |
||||
} |
||||
|
||||
/** |
||||
* Inserts a row into the associated Mongo collection using the attribute values of this record. |
||||
* |
||||
* This method performs the following steps in order: |
||||
* |
||||
* 1. call [[beforeValidate()]] when `$runValidation` is true. If validation |
||||
* fails, it will skip the rest of the steps; |
||||
* 2. call [[afterValidate()]] when `$runValidation` is true. |
||||
* 3. call [[beforeSave()]]. If the method returns false, it will skip the |
||||
* rest of the steps; |
||||
* 4. insert the record into collection. If this fails, it will skip the rest of the steps; |
||||
* 5. call [[afterSave()]]; |
||||
* |
||||
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], |
||||
* [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] |
||||
* will be raised by the corresponding methods. |
||||
* |
||||
* Only the [[dirtyAttributes|changed attribute values]] will be inserted into database. |
||||
* |
||||
* If the primary key is null during insertion, it will be populated with the actual |
||||
* value after insertion. |
||||
* |
||||
* For example, to insert a customer record: |
||||
* |
||||
* ~~~ |
||||
* $customer = new Customer; |
||||
* $customer->name = $name; |
||||
* $customer->email = $email; |
||||
* $customer->insert(); |
||||
* ~~~ |
||||
* |
||||
* @param boolean $runValidation whether to perform validation before saving the record. |
||||
* If the validation fails, the record will not be inserted into the collection. |
||||
* @param array $attributes list of attributes that need to be saved. Defaults to null, |
||||
* meaning all attributes that are loaded will be saved. |
||||
* @return boolean whether the attributes are valid and the record is inserted successfully. |
||||
* @throws \Exception in case insert failed. |
||||
*/ |
||||
public function insert($runValidation = true, $attributes = null) |
||||
{ |
||||
if ($runValidation && !$this->validate($attributes)) { |
||||
return false; |
||||
} |
||||
$result = $this->insertInternal($attributes); |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* @see ActiveRecord::insert() |
||||
*/ |
||||
protected function insertInternal($attributes = null) |
||||
{ |
||||
if (!$this->beforeSave(true)) { |
||||
return false; |
||||
} |
||||
$values = $this->getDirtyAttributes($attributes); |
||||
if (empty($values)) { |
||||
$currentAttributes = $this->getAttributes(); |
||||
foreach ($this->primaryKey() as $key) { |
||||
$values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null; |
||||
} |
||||
} |
||||
$collection = static::getCollection(); |
||||
$newId = $collection->insert($values); |
||||
$this->setAttribute('_id', $newId); |
||||
foreach ($values as $name => $value) { |
||||
$this->setOldAttribute($name, $value); |
||||
} |
||||
$this->afterSave(true); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* @see ActiveRecord::update() |
||||
* @throws StaleObjectException |
||||
*/ |
||||
protected function updateInternal($attributes = null) |
||||
{ |
||||
if (!$this->beforeSave(false)) { |
||||
return false; |
||||
} |
||||
$values = $this->getDirtyAttributes($attributes); |
||||
if (empty($values)) { |
||||
$this->afterSave(false); |
||||
return 0; |
||||
} |
||||
$condition = $this->getOldPrimaryKey(true); |
||||
$lock = $this->optimisticLock(); |
||||
if ($lock !== null) { |
||||
if (!isset($values[$lock])) { |
||||
$values[$lock] = $this->$lock + 1; |
||||
} |
||||
$condition[$lock] = $this->$lock; |
||||
} |
||||
// We do not check the return value of update() because it's possible |
||||
// that it doesn't change anything and thus returns 0. |
||||
$rows = static::getCollection()->update($condition, $values); |
||||
|
||||
if ($lock !== null && !$rows) { |
||||
throw new StaleObjectException('The object being updated is outdated.'); |
||||
} |
||||
|
||||
foreach ($values as $name => $value) { |
||||
$this->setOldAttribute($name, $this->getAttribute($name)); |
||||
} |
||||
$this->afterSave(false); |
||||
return $rows; |
||||
} |
||||
|
||||
/** |
||||
* Deletes the document corresponding to this active record from the collection. |
||||
* |
||||
* This method performs the following steps in order: |
||||
* |
||||
* 1. call [[beforeDelete()]]. If the method returns false, it will skip the |
||||
* rest of the steps; |
||||
* 2. delete the document from the collection; |
||||
* 3. call [[afterDelete()]]. |
||||
* |
||||
* In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] |
||||
* will be raised by the corresponding methods. |
||||
* |
||||
* @return integer|boolean the number of documents deleted, or false if the deletion is unsuccessful for some reason. |
||||
* Note that it is possible the number of documents deleted is 0, even though the deletion execution is successful. |
||||
* @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data |
||||
* being deleted is outdated. |
||||
* @throws \Exception in case delete failed. |
||||
*/ |
||||
public function delete() |
||||
{ |
||||
$result = false; |
||||
if ($this->beforeDelete()) { |
||||
$result = $this->deleteInternal(); |
||||
$this->afterDelete(); |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* @see ActiveRecord::delete() |
||||
* @throws StaleObjectException |
||||
*/ |
||||
protected function deleteInternal() |
||||
{ |
||||
// we do not check the return value of deleteAll() because it's possible |
||||
// the record is already deleted in the database and thus the method will return 0 |
||||
$condition = $this->getOldPrimaryKey(true); |
||||
$lock = $this->optimisticLock(); |
||||
if ($lock !== null) { |
||||
$condition[$lock] = $this->$lock; |
||||
} |
||||
$result = static::getCollection()->remove($condition); |
||||
if ($lock !== null && !$result) { |
||||
throw new StaleObjectException('The object being deleted is outdated.'); |
||||
} |
||||
$this->setOldAttributes(null); |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Returns a value indicating whether the given active record is the same as the current one. |
||||
* The comparison is made by comparing the table names and the primary key values of the two active records. |
||||
* If one of the records [[isNewRecord|is new]] they are also considered not equal. |
||||
* @param ActiveRecord $record record to compare to |
||||
* @return boolean whether the two active records refer to the same row in the same Mongo collection. |
||||
*/ |
||||
public function equals($record) |
||||
{ |
||||
if ($this->isNewRecord || $record->isNewRecord) { |
||||
return false; |
||||
} |
||||
return $this->collectionName() === $record->collectionName() && $this->getPrimaryKey() === $record->getPrimaryKey(); |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo; |
||||
|
||||
use yii\db\ActiveRelationInterface; |
||||
use yii\db\ActiveRelationTrait; |
||||
|
||||
/** |
||||
* ActiveRelation represents a relation to Mongo Active Record class. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface |
||||
{ |
||||
use ActiveRelationTrait; |
||||
} |
@ -0,0 +1,899 @@
|
||||
<?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; |
||||
use yii\helpers\Json; |
||||
|
||||
/** |
||||
* Collection represents the Mongo collection information. |
||||
* |
||||
* A collection object is usually created by calling [[Database::getCollection()]] or [[Connection::getCollection()]]. |
||||
* |
||||
* Collection provides the basic interface for the Mongo queries, mostly: insert, update, delete operations. |
||||
* For example: |
||||
* |
||||
* ~~~ |
||||
* $collection = Yii::$app->mongo->getCollection('customer'); |
||||
* $collection->insert(['name' => 'John Smith', 'status' => 1]); |
||||
* ~~~ |
||||
* |
||||
* To perform "find" queries, please use [[Query]] instead. |
||||
* |
||||
* Mongo uses JSON format to specify query conditions with quite specific syntax. |
||||
* However Collection class provides the ability of "translating" common condition format used "yii\db\*" |
||||
* into Mongo condition. |
||||
* For example: |
||||
* ~~~ |
||||
* $condition = [ |
||||
* [ |
||||
* 'OR', |
||||
* ['AND', ['first_name' => 'John'], ['last_name' => 'Smith']], |
||||
* ['status' => [1, 2, 3]] |
||||
* ], |
||||
* ]; |
||||
* print_r($collection->buildCondition($condition)); |
||||
* // outputs : |
||||
* [ |
||||
* '$or' => [ |
||||
* [ |
||||
* 'first_name' => 'John', |
||||
* 'last_name' => 'John', |
||||
* ], |
||||
* [ |
||||
* 'status' => ['$in' => [1, 2, 3]], |
||||
* ] |
||||
* ] |
||||
* ] |
||||
* ~~~ |
||||
* |
||||
* Note: condition values for the key '_id' will be automatically cast to [[\MongoId]] instance, |
||||
* even if they are plain strings. However if you have other columns, containing [[\MongoId]], you |
||||
* should take care of possible typecast on your own. |
||||
* |
||||
* @property string $name name of this collection. This property is read-only. |
||||
* @property string $fullName full name of this collection, including database name. This property is read-only. |
||||
* @property array $lastError last error information. This property is read-only. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Collection extends Object |
||||
{ |
||||
/** |
||||
* @var \MongoCollection Mongo collection instance. |
||||
*/ |
||||
public $mongoCollection; |
||||
|
||||
/** |
||||
* @return string name of this collection. |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return $this->mongoCollection->getName(); |
||||
} |
||||
|
||||
/** |
||||
* @return string full name of this collection, including database name. |
||||
*/ |
||||
public function getFullName() |
||||
{ |
||||
return $this->mongoCollection->__toString(); |
||||
} |
||||
|
||||
/** |
||||
* @return array last error information. |
||||
*/ |
||||
public function getLastError() |
||||
{ |
||||
return $this->mongoCollection->db->lastError(); |
||||
} |
||||
|
||||
/** |
||||
* Composes log/profile token. |
||||
* @param string $command command name |
||||
* @param array $arguments command arguments. |
||||
* @return string token. |
||||
*/ |
||||
protected function composeLogToken($command, $arguments = []) |
||||
{ |
||||
$parts = []; |
||||
foreach ($arguments as $argument) { |
||||
$parts[] = is_scalar($argument) ? $argument : Json::encode($argument); |
||||
} |
||||
return $this->getFullName() . '.' . $command . '(' . implode(', ', $parts) . ')'; |
||||
} |
||||
|
||||
/** |
||||
* Drops this collection. |
||||
* @throws Exception on failure. |
||||
* @return boolean whether the operation successful. |
||||
*/ |
||||
public function drop() |
||||
{ |
||||
$token = $this->composeLogToken('drop'); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->drop(); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return true; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates an index on the collection and the specified fields. |
||||
* @param array|string $columns column name or list of column names. |
||||
* If array is given, each element in the array has as key the field name, and as |
||||
* value either 1 for ascending sort, or -1 for descending sort. |
||||
* You can specify field using native numeric key with the field name as a value, |
||||
* in this case ascending sort will be used. |
||||
* For example: |
||||
* ~~~ |
||||
* [ |
||||
* 'name', |
||||
* 'status' => -1, |
||||
* ] |
||||
* ~~~ |
||||
* @param array $options list of options in format: optionName => optionValue. |
||||
* @throws Exception on failure. |
||||
* @return boolean whether the operation successful. |
||||
*/ |
||||
public function createIndex($columns, $options = []) |
||||
{ |
||||
if (!is_array($columns)) { |
||||
$columns = [$columns]; |
||||
} |
||||
$keys = $this->normalizeIndexKeys($columns); |
||||
$token = $this->composeLogToken('createIndex', [$keys, $options]); |
||||
$options = array_merge(['w' => 1], $options); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->ensureIndex($keys, $options); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return true; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Drop indexes for specified column(s). |
||||
* @param string|array $columns column name or list of column names. |
||||
* If array is given, each element in the array has as key the field name, and as |
||||
* value either 1 for ascending sort, or -1 for descending sort. |
||||
* Use value 'text' to specify text index. |
||||
* You can specify field using native numeric key with the field name as a value, |
||||
* in this case ascending sort will be used. |
||||
* For example: |
||||
* ~~~ |
||||
* [ |
||||
* 'name', |
||||
* 'status' => -1, |
||||
* 'description' => 'text', |
||||
* ] |
||||
* ~~~ |
||||
* @throws Exception on failure. |
||||
* @return boolean whether the operation successful. |
||||
*/ |
||||
public function dropIndex($columns) |
||||
{ |
||||
if (!is_array($columns)) { |
||||
$columns = [$columns]; |
||||
} |
||||
$keys = $this->normalizeIndexKeys($columns); |
||||
$token = $this->composeLogToken('dropIndex', [$keys]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
$result = $this->mongoCollection->deleteIndex($keys); |
||||
$this->tryResultError($result); |
||||
return true; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Compose index keys from given columns/keys list. |
||||
* @param array $columns raw columns/keys list. |
||||
* @return array normalizes index keys array. |
||||
*/ |
||||
protected function normalizeIndexKeys($columns) |
||||
{ |
||||
$keys = []; |
||||
foreach ($columns as $key => $value) { |
||||
if (is_numeric($key)) { |
||||
$keys[$value] = \MongoCollection::ASCENDING; |
||||
} else { |
||||
$keys[$key] = $value; |
||||
} |
||||
} |
||||
return $keys; |
||||
} |
||||
|
||||
/** |
||||
* Drops all indexes for this collection. |
||||
* @throws Exception on failure. |
||||
* @return integer count of dropped indexes. |
||||
*/ |
||||
public function dropAllIndexes() |
||||
{ |
||||
$token = $this->composeLogToken('dropIndexes'); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
$result = $this->mongoCollection->deleteIndexes(); |
||||
$this->tryResultError($result); |
||||
return $result['nIndexesWas']; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a cursor for the search results. |
||||
* In order to perform "find" queries use [[Query]] class. |
||||
* @param array $condition query condition |
||||
* @param array $fields fields to be selected |
||||
* @return \MongoCursor cursor for the search results |
||||
* @see Query |
||||
*/ |
||||
public function find($condition = [], $fields = []) |
||||
{ |
||||
return $this->mongoCollection->find($this->buildCondition($condition), $fields); |
||||
} |
||||
|
||||
/** |
||||
* 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 = $this->composeLogToken('insert', [$data]); |
||||
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 = $this->composeLogToken('batchInsert', [$rows]); |
||||
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. |
||||
* Note: for "multiple" mode Mongo requires explicit strategy "$set" or "$inc" |
||||
* to be specified for the "newData". If no strategy is passed "$set" will be used. |
||||
* @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 = []) |
||||
{ |
||||
$condition = $this->buildCondition($condition); |
||||
$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]; |
||||
} |
||||
} |
||||
$token = $this->composeLogToken('update', [$condition, $newData, $options]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$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 = $this->composeLogToken('save', [$data]); |
||||
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 = []) |
||||
{ |
||||
$condition = $this->buildCondition($condition); |
||||
$options = array_merge(['w' => 1, 'multiple' => true], $options); |
||||
$token = $this->composeLogToken('remove', [$condition, $options]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->remove($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); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a list of distinct values for the given column across a collection. |
||||
* @param string $column column to use. |
||||
* @param array $condition query parameters. |
||||
* @return array|boolean array of distinct values, or "false" on failure. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function distinct($column, $condition = []) |
||||
{ |
||||
$condition = $this->buildCondition($condition); |
||||
$token = $this->composeLogToken('distinct', [$column, $condition]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->distinct($column, $condition); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Performs aggregation using Mongo Aggregation Framework. |
||||
* @param array $pipeline list of pipeline operators, or just the first operator |
||||
* @param array $pipelineOperator additional pipeline operator. You can specify additional |
||||
* pipelines via third argument, fourth argument etc. |
||||
* @return array the result of the aggregation. |
||||
* @throws Exception on failure. |
||||
* @see http://docs.mongodb.org/manual/applications/aggregation/ |
||||
*/ |
||||
public function aggregate($pipeline, $pipelineOperator = []) |
||||
{ |
||||
$args = func_get_args(); |
||||
$token = $this->composeLogToken('aggregate', $args); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = call_user_func_array([$this->mongoCollection, 'aggregate'], $args); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result['result']; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Performs aggregation using Mongo "group" command. |
||||
* @param mixed $keys fields to group by. If an array or non-code object is passed, |
||||
* it will be the key used to group results. If instance of [[\MongoCode]] passed, |
||||
* it will be treated as a function that returns the key to group by. |
||||
* @param array $initial Initial value of the aggregation counter object. |
||||
* @param \MongoCode|string $reduce function that takes two arguments (the current |
||||
* document and the aggregation to this point) and does the aggregation. |
||||
* Argument will be automatically cast to [[\MongoCode]]. |
||||
* @param array $options optional parameters to the group command. Valid options include: |
||||
* - condition - criteria for including a document in the aggregation. |
||||
* - finalize - function called once per unique key that takes the final output of the reduce function. |
||||
* @return array the result of the aggregation. |
||||
* @throws Exception on failure. |
||||
* @see http://docs.mongodb.org/manual/reference/command/group/ |
||||
*/ |
||||
public function group($keys, $initial, $reduce, $options = []) |
||||
{ |
||||
if (!($reduce instanceof \MongoCode)) { |
||||
$reduce = new \MongoCode((string)$reduce); |
||||
} |
||||
if (array_key_exists('condition', $options)) { |
||||
$options['condition'] = $this->buildCondition($options['condition']); |
||||
} |
||||
if (array_key_exists('finalize', $options)) { |
||||
if (!($options['finalize'] instanceof \MongoCode)) { |
||||
$options['finalize'] = new \MongoCode((string)$options['finalize']); |
||||
} |
||||
} |
||||
$token = $this->composeLogToken('group', [$keys, $initial, $reduce, $options]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
// Avoid possible E_DEPRECATED for $options: |
||||
if (empty($options)) { |
||||
$result = $this->mongoCollection->group($keys, $initial, $reduce); |
||||
} else { |
||||
$result = $this->mongoCollection->group($keys, $initial, $reduce, $options); |
||||
} |
||||
$this->tryResultError($result); |
||||
|
||||
Yii::endProfile($token, __METHOD__); |
||||
if (array_key_exists('retval', $result)) { |
||||
return $result['retval']; |
||||
} else { |
||||
return []; |
||||
} |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Performs aggregation using Mongo "map reduce" mechanism. |
||||
* Note: this function will not return the aggregation result, instead it will |
||||
* write it inside the another Mongo collection specified by "out" parameter. |
||||
* For example: |
||||
* |
||||
* ~~~ |
||||
* $customerCollection = Yii::$app->mongo->getCollection('customer'); |
||||
* $resultCollectionName = $customerCollection->mapReduce( |
||||
* 'function () {emit(this.status, this.amount)}', |
||||
* 'function (key, values) {return Array.sum(values)}', |
||||
* 'mapReduceOut', |
||||
* ['status' => 3] |
||||
* ); |
||||
* $query = new Query(); |
||||
* $results = $query->from($resultCollectionName)->all(); |
||||
* ~~~ |
||||
* |
||||
* @param \MongoCode|string $map function, which emits map data from collection. |
||||
* Argument will be automatically cast to [[\MongoCode]]. |
||||
* @param \MongoCode|string $reduce function that takes two arguments (the map key |
||||
* and the map values) and does the aggregation. |
||||
* Argument will be automatically cast to [[\MongoCode]]. |
||||
* @param string|array $out output collection name. It could be a string for simple output |
||||
* ('outputCollection'), or an array for parametrized output (['merge' => 'outputCollection']) |
||||
* @param array $condition criteria for including a document in the aggregation. |
||||
* @return string the map reduce output collection name. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function mapReduce($map, $reduce, $out, $condition = []) |
||||
{ |
||||
if (!($map instanceof \MongoCode)) { |
||||
$map = new \MongoCode((string)$map); |
||||
} |
||||
if (!($reduce instanceof \MongoCode)) { |
||||
$reduce = new \MongoCode((string)$reduce); |
||||
} |
||||
$command = [ |
||||
'mapReduce' => $this->getName(), |
||||
'map' => $map, |
||||
'reduce' => $reduce, |
||||
'out' => $out |
||||
]; |
||||
if (!empty($condition)) { |
||||
$command['query'] = $this->buildCondition($condition); |
||||
} |
||||
$token = $this->composeLogToken('mapReduce', [$map, $reduce, $out]); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$command = array_merge(['mapReduce' => $this->getName()], $command); |
||||
$result = $this->mongoCollection->db->command($command); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result['result']; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Performs full text search. |
||||
* @param string $search string of terms that MongoDB parses and uses to query the text index. |
||||
* @param array $condition criteria for filtering a results list. |
||||
* @param array $fields list of fields to be returned in result. |
||||
* @param integer $limit the maximum number of documents to include in the response (by default 100). |
||||
* @param string $language he language that determines the list of stop words for the search |
||||
* and the rules for the stemmer and tokenizer. If not specified, the search uses the default |
||||
* language of the index. |
||||
* @return array the highest scoring documents, in descending order by score. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function fullTextSearch($search, $condition = [], $fields = [], $limit = null, $language = null) { |
||||
$command = [ |
||||
'search' => $search |
||||
]; |
||||
if (!empty($condition)) { |
||||
$command['filter'] = $this->buildCondition($condition); |
||||
} |
||||
if (!empty($fields)) { |
||||
$command['project'] = $fields; |
||||
} |
||||
if ($limit !== null) { |
||||
$command['limit'] = $limit; |
||||
} |
||||
if ($language !== null) { |
||||
$command['language'] = $language; |
||||
} |
||||
$token = $this->composeLogToken('text', $command); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$command = array_merge(['text' => $this->getName()], $command); |
||||
$result = $this->mongoCollection->db->command($command); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result['results']; |
||||
} 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['errmsg'])) { |
||||
$errorMessage = $result['errmsg']; |
||||
} elseif (!empty($result['err'])) { |
||||
$errorMessage = $result['err']; |
||||
} |
||||
if (isset($errorMessage)) { |
||||
if (array_key_exists('code', $result)) { |
||||
$errorCode = (int)$result['code']; |
||||
} elseif (array_key_exists('ok', $result)) { |
||||
$errorCode = (int)$result['ok']; |
||||
} else { |
||||
$errorCode = 0; |
||||
} |
||||
throw new Exception($errorMessage, $errorCode); |
||||
} |
||||
} elseif (!$result) { |
||||
throw new Exception('Unknown error, use "w=1" option to enable error tracking'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Throws an exception if there was an error on the last operation. |
||||
* @throws Exception if an error occurred. |
||||
*/ |
||||
protected function tryLastError() |
||||
{ |
||||
$this->tryResultError($this->getLastError()); |
||||
} |
||||
|
||||
/** |
||||
* 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' |
||||
]; |
||||
$matchKey = strtoupper($key); |
||||
if (array_key_exists($matchKey, $map)) { |
||||
return $map[$matchKey]; |
||||
} else { |
||||
return $key; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Converts given value into [[MongoId]] instance. |
||||
* If array given, each element of it will be processed. |
||||
* @param mixed $rawId raw id(s). |
||||
* @return array|\MongoId normalized id(s). |
||||
*/ |
||||
protected function ensureMongoId($rawId) |
||||
{ |
||||
if (is_array($rawId)) { |
||||
$result = []; |
||||
foreach ($rawId as $key => $value) { |
||||
$result[$key] = $this->ensureMongoId($value); |
||||
} |
||||
return $result; |
||||
} elseif (is_object($rawId)) { |
||||
if ($rawId instanceof \MongoId) { |
||||
return $rawId; |
||||
} else { |
||||
$rawId = (string)$rawId; |
||||
} |
||||
} |
||||
return new \MongoId($rawId); |
||||
} |
||||
|
||||
/** |
||||
* Parses the condition specification and generates the corresponding Mongo condition. |
||||
* @param array $condition the condition specification. Please refer to [[Query::where()]] |
||||
* on how to specify a condition. |
||||
* @return array the generated Mongo condition |
||||
* @throws InvalidParamException if the condition is in bad format |
||||
*/ |
||||
public function buildCondition($condition) |
||||
{ |
||||
static $builders = [ |
||||
'AND' => 'buildAndCondition', |
||||
'OR' => 'buildOrCondition', |
||||
'BETWEEN' => 'buildBetweenCondition', |
||||
'NOT BETWEEN' => 'buildBetweenCondition', |
||||
'IN' => 'buildInCondition', |
||||
'NOT IN' => 'buildInCondition', |
||||
'LIKE' => 'buildLikeCondition', |
||||
]; |
||||
|
||||
if (!is_array($condition)) { |
||||
throw new InvalidParamException('Condition should be an array.'); |
||||
} elseif (empty($condition)) { |
||||
return []; |
||||
} |
||||
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... |
||||
$operator = strtoupper($condition[0]); |
||||
if (isset($builders[$operator])) { |
||||
$method = $builders[$operator]; |
||||
array_shift($condition); |
||||
return $this->$method($operator, $condition); |
||||
} else { |
||||
throw new InvalidParamException('Found unknown operator in query: ' . $operator); |
||||
} |
||||
} else { |
||||
// hash format: 'column1' => 'value1', 'column2' => 'value2', ... |
||||
return $this->buildHashCondition($condition); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates a condition based on column-value pairs. |
||||
* @param array $condition the condition specification. |
||||
* @return array the generated Mongo condition. |
||||
*/ |
||||
public function buildHashCondition($condition) |
||||
{ |
||||
$result = []; |
||||
foreach ($condition as $name => $value) { |
||||
$name = $this->normalizeConditionKeyword($name); |
||||
if (strncmp('$', $name, 1) === 0) { |
||||
// Native Mongo condition: |
||||
$result[$name] = $value; |
||||
} else { |
||||
if (is_array($value)) { |
||||
if (array_key_exists(0, $value)) { |
||||
// Quick IN condition: |
||||
$result = array_merge($result, $this->buildInCondition('IN', [$name, $value])); |
||||
} else { |
||||
// Normalize possible verbose condition: |
||||
$actualValue = []; |
||||
foreach ($value as $k => $v) { |
||||
$actualValue[$this->normalizeConditionKeyword($k)] = $v; |
||||
} |
||||
$result[$name] = $actualValue; |
||||
} |
||||
} else { |
||||
// Direct match: |
||||
if ($name == '_id') { |
||||
$value = $this->ensureMongoId($value); |
||||
} |
||||
$result[$name] = $value; |
||||
} |
||||
} |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Connects two or more conditions with the `AND` operator. |
||||
* @param string $operator the operator to use for connecting the given operands |
||||
* @param array $operands the Mongo conditions to connect. |
||||
* @return array the generated Mongo condition. |
||||
*/ |
||||
public function buildAndCondition($operator, $operands) |
||||
{ |
||||
$result = []; |
||||
foreach ($operands as $operand) { |
||||
$condition = $this->buildCondition($operand); |
||||
$result = array_merge_recursive($result, $condition); |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Connects two or more conditions with the `OR` operator. |
||||
* @param string $operator the operator to use for connecting the given operands |
||||
* @param array $operands the Mongo conditions to connect. |
||||
* @return array the generated Mongo condition. |
||||
*/ |
||||
public function buildOrCondition($operator, $operands) |
||||
{ |
||||
$operator = $this->normalizeConditionKeyword($operator); |
||||
$parts = []; |
||||
foreach ($operands as $operand) { |
||||
$parts[] = $this->buildCondition($operand); |
||||
} |
||||
return [$operator => $parts]; |
||||
} |
||||
|
||||
/** |
||||
* Creates an Mongo condition, which emulates the `BETWEEN` operator. |
||||
* @param string $operator the operator to use |
||||
* @param array $operands the first operand is the column name. The second and third operands |
||||
* describe the interval that column value should be in. |
||||
* @return array the generated Mongo condition. |
||||
* @throws InvalidParamException if wrong number of operands have been given. |
||||
*/ |
||||
public function buildBetweenCondition($operator, $operands) |
||||
{ |
||||
if (!isset($operands[0], $operands[1], $operands[2])) { |
||||
throw new InvalidParamException("Operator '$operator' requires three operands."); |
||||
} |
||||
list($column, $value1, $value2) = $operands; |
||||
if (strncmp('NOT', $operator, 3) === 0) { |
||||
return [ |
||||
$column => [ |
||||
'$lt' => $value1, |
||||
'$gt' => $value2, |
||||
] |
||||
]; |
||||
} else { |
||||
return [ |
||||
$column => [ |
||||
'$gte' => $value1, |
||||
'$lte' => $value2, |
||||
] |
||||
]; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates an Mongo condition with the `IN` operator. |
||||
* @param string $operator the operator to use (e.g. `IN` or `NOT IN`) |
||||
* @param array $operands the first operand is the column name. If it is an array |
||||
* a composite IN condition will be generated. |
||||
* The second operand is an array of values that column value should be among. |
||||
* @return array the generated Mongo condition. |
||||
* @throws InvalidParamException if wrong number of operands have been given. |
||||
*/ |
||||
public function buildInCondition($operator, $operands) |
||||
{ |
||||
if (!isset($operands[0], $operands[1])) { |
||||
throw new InvalidParamException("Operator '$operator' requires two operands."); |
||||
} |
||||
|
||||
list($column, $values) = $operands; |
||||
|
||||
$values = (array)$values; |
||||
|
||||
if (!is_array($column)) { |
||||
$columns = [$column]; |
||||
$values = [$column => $values]; |
||||
} elseif (count($column) < 2) { |
||||
$columns = $column; |
||||
$values = [$column[0] => $values]; |
||||
} else { |
||||
$columns = $column; |
||||
} |
||||
|
||||
$operator = $this->normalizeConditionKeyword($operator); |
||||
$result = []; |
||||
foreach ($columns as $column) { |
||||
if ($column == '_id') { |
||||
$inValues = $this->ensureMongoId($values[$column]); |
||||
} else { |
||||
$inValues = $values[$column]; |
||||
} |
||||
$result[$column][$operator] = $inValues; |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Creates a Mongo condition, which emulates the `LIKE` operator. |
||||
* @param string $operator the operator to use |
||||
* @param array $operands the first operand is the column name. |
||||
* The second operand is a single value that column value should be compared with. |
||||
* @return array the generated Mongo condition. |
||||
* @throws InvalidParamException if wrong number of operands have been given. |
||||
*/ |
||||
public function buildLikeCondition($operator, $operands) |
||||
{ |
||||
if (!isset($operands[0], $operands[1])) { |
||||
throw new InvalidParamException("Operator '$operator' requires two operands."); |
||||
} |
||||
list($column, $value) = $operands; |
||||
return [$column => '/' . $value . '/']; |
||||
} |
||||
} |
@ -0,0 +1,253 @@
|
||||
<?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\Component; |
||||
use yii\base\InvalidConfigException; |
||||
use Yii; |
||||
|
||||
/** |
||||
* Connection represents a connection to a MongoDb server. |
||||
* |
||||
* Connection works together with [[Database]] and [[Collection]] to provide data access |
||||
* to the Mongo database. They are wrappers of the [[MongoDB PHP extension]](http://us1.php.net/manual/en/book.mongo.php). |
||||
* |
||||
* To establish a DB connection, set [[dsn]] and then call [[open()]] to be true. |
||||
* |
||||
* The following example shows how to create a Connection instance and establish |
||||
* the DB connection: |
||||
* |
||||
* ~~~ |
||||
* $connection = new \yii\mongo\Connection([ |
||||
* 'dsn' => $dsn, |
||||
* ]); |
||||
* $connection->open(); |
||||
* ~~~ |
||||
* |
||||
* After the Mongo connection is established, one can access Mongo databases and collections: |
||||
* |
||||
* ~~~ |
||||
* $database = $connection->getDatabase('my_mongo_db'); |
||||
* $collection = $database->getCollection('customer'); |
||||
* $collection->insert(['name' => 'John Smith', 'status' => 1]); |
||||
* ~~~ |
||||
* |
||||
* You can work with several different databases at the same server using this class. |
||||
* However, while it is unlikely your application will actually need it, the Connection class |
||||
* provides ability to use [[defaultDatabaseName]] as well as a shortcut method [[getCollection()]] |
||||
* to retrieve a particular collection instance: |
||||
* |
||||
* ~~~ |
||||
* // get collection 'customer' from default database: |
||||
* $collection = $connection->getCollection('customer'); |
||||
* // get collection 'customer' from database 'mydatabase': |
||||
* $collection = $connection->getCollection(['mydatabase', 'customer']); |
||||
* ~~~ |
||||
* |
||||
* Connection is often used as an application component and configured in the application |
||||
* configuration like the following: |
||||
* |
||||
* ~~~ |
||||
* [ |
||||
* 'components' => [ |
||||
* 'mongo' => [ |
||||
* 'class' => '\yii\mongo\Connection', |
||||
* 'dsn' => 'mongodb://developer:password@localhost:27017/mydatabase', |
||||
* ], |
||||
* ], |
||||
* ] |
||||
* ~~~ |
||||
* |
||||
* @property boolean $isActive Whether the Mongo connection is established. This property is read-only. |
||||
* is read-only. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Connection extends Component |
||||
{ |
||||
/** |
||||
* @var string host:port |
||||
* |
||||
* Correct syntax is: |
||||
* mongodb://[username:password@]host1[:port1][,host2[:port2:],...][/dbname] |
||||
* For example: |
||||
* mongodb://localhost:27017 |
||||
* mongodb://developer:password@localhost:27017 |
||||
* mongodb://developer:password@localhost:27017/mydatabase |
||||
*/ |
||||
public $dsn; |
||||
/** |
||||
* @var array connection options. |
||||
* for example: |
||||
* ~~~ |
||||
* [ |
||||
* 'persist' => true, // use persistent connection |
||||
* 'socketTimeoutMS' => 1000, // how long a send or receive on a socket can take before timing out |
||||
* 'journal' => true // block write operations until the journal be flushed the to disk |
||||
* ] |
||||
* ~~~ |
||||
*/ |
||||
public $options = []; |
||||
/** |
||||
* @var string name of the Mongo database to use by default. |
||||
* If this field left blank, connection instance will attempt to determine it from |
||||
* [[options]] and [[dsn]] automatically, if needed. |
||||
*/ |
||||
public $defaultDatabaseName; |
||||
/** |
||||
* @var \MongoClient mongo client instance. |
||||
*/ |
||||
public $mongoClient; |
||||
/** |
||||
* @var Database[] list of Mongo databases |
||||
*/ |
||||
private $_databases = []; |
||||
|
||||
/** |
||||
* Returns the Mongo collection with the given name. |
||||
* @param string|null $name collection name, if null default one will be used. |
||||
* @param boolean $refresh whether to reload the table schema even if it is found in the cache. |
||||
* @return Database database instance. |
||||
*/ |
||||
public function getDatabase($name = null, $refresh = false) |
||||
{ |
||||
if ($name === null) { |
||||
$name = $this->fetchDefaultDatabaseName(); |
||||
} |
||||
if ($refresh || !array_key_exists($name, $this->_databases)) { |
||||
$this->_databases[$name] = $this->selectDatabase($name); |
||||
} |
||||
return $this->_databases[$name]; |
||||
} |
||||
|
||||
/** |
||||
* Returns [[defaultDatabaseName]] value, if it is not set, |
||||
* attempts to determine it from [[dsn]] value. |
||||
* @return string default database name |
||||
* @throws \yii\base\InvalidConfigException if unable to determine default database name. |
||||
*/ |
||||
protected function fetchDefaultDatabaseName() |
||||
{ |
||||
if ($this->defaultDatabaseName === null) { |
||||
if (isset($this->options['db'])) { |
||||
$this->defaultDatabaseName = $this->options['db']; |
||||
} elseif (preg_match('/^mongodb:\\/\\/.+\\/(.+)$/s', $this->dsn, $matches)) { |
||||
$this->defaultDatabaseName = $matches[1]; |
||||
} else { |
||||
throw new InvalidConfigException("Unable to determine default database name from dsn."); |
||||
} |
||||
} |
||||
return $this->defaultDatabaseName; |
||||
} |
||||
|
||||
/** |
||||
* Selects the database with given name. |
||||
* @param string $name database name. |
||||
* @return Database database instance. |
||||
*/ |
||||
protected function selectDatabase($name) |
||||
{ |
||||
$this->open(); |
||||
return Yii::createObject([ |
||||
'class' => 'yii\mongo\Database', |
||||
'mongoDb' => $this->mongoClient->selectDB($name) |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo collection with the given name. |
||||
* @param string|array $name collection name. If string considered as the name of the collection |
||||
* inside the default database. If array - first element considered as the name of the database, |
||||
* second - as name of collection inside that database |
||||
* @param boolean $refresh whether to reload the collection instance even if it is found in the cache. |
||||
* @return Collection Mongo collection instance. |
||||
*/ |
||||
public function getCollection($name, $refresh = false) |
||||
{ |
||||
if (is_array($name)) { |
||||
list ($dbName, $collectionName) = $name; |
||||
return $this->getDatabase($dbName)->getCollection($collectionName, $refresh); |
||||
} else { |
||||
return $this->getDatabase()->getCollection($name, $refresh); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo GridFS collection. |
||||
* @param string|array $prefix collection prefix. If string considered as the prefix of the GridFS |
||||
* collection inside the default database. If array - first element considered as the name of the database, |
||||
* second - as prefix of the GridFS collection inside that database, if no second element present |
||||
* default "fs" prefix will be used. |
||||
* @param boolean $refresh whether to reload the collection instance even if it is found in the cache. |
||||
* @return file\Collection Mongo GridFS collection instance. |
||||
*/ |
||||
public function getFileCollection($prefix = 'fs', $refresh = false) |
||||
{ |
||||
if (is_array($prefix)) { |
||||
list ($dbName, $collectionPrefix) = $prefix; |
||||
if (!isset($collectionPrefix)) { |
||||
$collectionPrefix = 'fs'; |
||||
} |
||||
return $this->getDatabase($dbName)->getFileCollection($collectionPrefix, $refresh); |
||||
} else { |
||||
return $this->getDatabase()->getFileCollection($prefix, $refresh); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a value indicating whether the Mongo connection is established. |
||||
* @return boolean whether the Mongo connection is established |
||||
*/ |
||||
public function getIsActive() |
||||
{ |
||||
return is_object($this->mongoClient) && $this->mongoClient->connected; |
||||
} |
||||
|
||||
/** |
||||
* Establishes a Mongo connection. |
||||
* It does nothing if a Mongo connection has already been established. |
||||
* @throws Exception if connection fails |
||||
*/ |
||||
public function open() |
||||
{ |
||||
if ($this->mongoClient === null) { |
||||
if (empty($this->dsn)) { |
||||
throw new InvalidConfigException($this->className() . '::dsn cannot be empty.'); |
||||
} |
||||
$token = 'Opening Mongo connection: ' . $this->dsn; |
||||
try { |
||||
Yii::trace($token, __METHOD__); |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$options = $this->options; |
||||
$options['connect'] = true; |
||||
if ($this->defaultDatabaseName !== null) { |
||||
$options['db'] = $this->defaultDatabaseName; |
||||
} |
||||
$this->mongoClient = new \MongoClient($this->dsn, $options); |
||||
Yii::endProfile($token, __METHOD__); |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Closes the currently active DB connection. |
||||
* It does nothing if the connection is already closed. |
||||
*/ |
||||
public function close() |
||||
{ |
||||
if ($this->mongoClient !== null) { |
||||
Yii::trace('Closing Mongo connection: ' . $this->dsn, __METHOD__); |
||||
$this->mongoClient = null; |
||||
$this->_databases = []; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,172 @@
|
||||
<?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\Object; |
||||
use Yii; |
||||
use yii\helpers\Json; |
||||
|
||||
/** |
||||
* Database represents the Mongo database information. |
||||
* |
||||
* @property string $name name of this database. This property is read-only. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Database extends Object |
||||
{ |
||||
/** |
||||
* @var \MongoDB Mongo database instance. |
||||
*/ |
||||
public $mongoDb; |
||||
/** |
||||
* @var Collection[] list of collections. |
||||
*/ |
||||
private $_collections = []; |
||||
/** |
||||
* @var file\Collection[] list of GridFS collections. |
||||
*/ |
||||
private $_fileCollections = []; |
||||
|
||||
/** |
||||
* @return string name of this database. |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return $this->mongoDb->__toString(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo collection with the given name. |
||||
* @param string $name collection name |
||||
* @param boolean $refresh whether to reload the collection instance even if it is found in the cache. |
||||
* @return Collection mongo collection instance. |
||||
*/ |
||||
public function getCollection($name, $refresh = false) |
||||
{ |
||||
if ($refresh || !array_key_exists($name, $this->_collections)) { |
||||
$this->_collections[$name] = $this->selectCollection($name); |
||||
} |
||||
return $this->_collections[$name]; |
||||
} |
||||
|
||||
/** |
||||
* Returns Mongo GridFS collection with given prefix. |
||||
* @param string $prefix collection prefix. |
||||
* @param boolean $refresh whether to reload the collection instance even if it is found in the cache. |
||||
* @return file\Collection mongo GridFS collection. |
||||
*/ |
||||
public function getFileCollection($prefix = 'fs', $refresh = false) |
||||
{ |
||||
if ($refresh || !array_key_exists($prefix, $this->_fileCollections)) { |
||||
$this->_fileCollections[$prefix] = $this->selectFileCollection($prefix); |
||||
} |
||||
return $this->_fileCollections[$prefix]; |
||||
} |
||||
|
||||
/** |
||||
* Selects collection with given name. |
||||
* @param string $name collection name. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
protected function selectCollection($name) |
||||
{ |
||||
return Yii::createObject([ |
||||
'class' => 'yii\mongo\Collection', |
||||
'mongoCollection' => $this->mongoDb->selectCollection($name) |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Selects GridFS collection with given prefix. |
||||
* @param string $prefix file collection prefix. |
||||
* @return file\Collection file collection instance. |
||||
*/ |
||||
protected function selectFileCollection($prefix) |
||||
{ |
||||
return Yii::createObject([ |
||||
'class' => 'yii\mongo\file\Collection', |
||||
'mongoCollection' => $this->mongoDb->getGridFS($prefix) |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Creates new collection. |
||||
* Note: Mongo creates new collections automatically on the first demand, |
||||
* this method makes sense only for the migration script or for the case |
||||
* you need to create collection with the specific options. |
||||
* @param string $name name of the collection |
||||
* @param array $options collection options in format: "name" => "value" |
||||
* @return \MongoCollection new mongo collection instance. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function createCollection($name, $options = []) |
||||
{ |
||||
$token = $this->getName() . '.create(' . $name . ', ' . Json::encode($options) . ')'; |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoDb->createCollection($name, $options); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Executes Mongo command. |
||||
* @param array $command command specification. |
||||
* @param array $options options in format: "name" => "value" |
||||
* @return array database response. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function executeCommand($command, $options = []) |
||||
{ |
||||
$token = $this->getName() . '.$cmd(' . Json::encode($command) . ', ' . Json::encode($options) . ')'; |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoDb->command($command, $options); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} 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['errmsg'])) { |
||||
$errorMessage = $result['errmsg']; |
||||
} elseif (!empty($result['err'])) { |
||||
$errorMessage = $result['err']; |
||||
} |
||||
if (isset($errorMessage)) { |
||||
if (array_key_exists('ok', $result)) { |
||||
$errorCode = (int)$result['ok']; |
||||
} else { |
||||
$errorCode = 0; |
||||
} |
||||
throw new Exception($errorMessage, $errorCode); |
||||
} |
||||
} elseif (!$result) { |
||||
throw new Exception('Unknown error, use "w=1" option to enable error tracking'); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,25 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo; |
||||
|
||||
/** |
||||
* Exception represents an exception that is caused by some Mongo-related operations. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Exception extends \yii\base\Exception |
||||
{ |
||||
/** |
||||
* @return string the user-friendly name of this exception |
||||
*/ |
||||
public function getName() |
||||
{ |
||||
return \Yii::t('yii', 'Mongo Exception'); |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
The Yii framework is free software. It is released under the terms of |
||||
the following BSD License. |
||||
|
||||
Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in |
||||
the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Yii Software LLC nor the names of its |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,344 @@
|
||||
<?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\Component; |
||||
use yii\db\QueryInterface; |
||||
use yii\db\QueryTrait; |
||||
use yii\helpers\Json; |
||||
use Yii; |
||||
|
||||
/** |
||||
* Query represents Mongo "find" operation. |
||||
* |
||||
* Query provides a set of methods to facilitate the specification of "find" command. |
||||
* These methods can be chained together. |
||||
* |
||||
* For example, |
||||
* |
||||
* ~~~ |
||||
* $query = new Query; |
||||
* // compose the query |
||||
* $query->select(['name', 'status']) |
||||
* ->from('customer') |
||||
* ->limit(10); |
||||
* // execute the query |
||||
* $rows = $query->all(); |
||||
* ~~~ |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Query extends Component implements QueryInterface |
||||
{ |
||||
use QueryTrait; |
||||
|
||||
/** |
||||
* @var array the fields of the results to return. For example, `['name', 'group_id']`. |
||||
* The "_id" field is always returned. If not set, if means selecting all columns. |
||||
* @see select() |
||||
*/ |
||||
public $select = []; |
||||
/** |
||||
* @var string|array the collection to be selected from. If string considered as the name of the collection |
||||
* inside the default database. If array - first element considered as the name of the database, |
||||
* second - as name of collection inside that database |
||||
* @see from() |
||||
*/ |
||||
public $from; |
||||
|
||||
/** |
||||
* Returns the Mongo collection for this query. |
||||
* @param Connection $db Mongo connection. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public function getCollection($db = null) |
||||
{ |
||||
if ($db === null) { |
||||
$db = Yii::$app->getComponent('mongo'); |
||||
} |
||||
return $db->getCollection($this->from); |
||||
} |
||||
|
||||
/** |
||||
* Sets the list of fields of the results to return. |
||||
* @param array $fields fields of the results to return. |
||||
* @return static the query object itself. |
||||
*/ |
||||
public function select(array $fields) |
||||
{ |
||||
$this->select = $fields; |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Sets the collection to be selected from. |
||||
* @param string|array the collection to be selected from. If string considered as the name of the collection |
||||
* inside the default database. If array - first element considered as the name of the database, |
||||
* second - as name of collection inside that database |
||||
* @return static the query object itself. |
||||
*/ |
||||
public function from($collection) |
||||
{ |
||||
$this->from = $collection; |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Builds the Mongo cursor for this query. |
||||
* @param Connection $db the database connection used to execute the query. |
||||
* @return \MongoCursor mongo cursor instance. |
||||
*/ |
||||
protected function buildCursor($db = null) |
||||
{ |
||||
if ($this->where === null) { |
||||
$where = []; |
||||
} else { |
||||
$where = $this->where; |
||||
} |
||||
$selectFields = []; |
||||
if (!empty($this->select)) { |
||||
foreach ($this->select as $fieldName) { |
||||
$selectFields[$fieldName] = true; |
||||
} |
||||
} |
||||
$cursor = $this->getCollection($db)->find($where, $selectFields); |
||||
if (!empty($this->orderBy)) { |
||||
$sort = []; |
||||
foreach ($this->orderBy as $fieldName => $sortOrder) { |
||||
$sort[$fieldName] = $sortOrder === SORT_DESC ? \MongoCollection::DESCENDING : \MongoCollection::ASCENDING; |
||||
} |
||||
$cursor->sort($sort); |
||||
} |
||||
$cursor->limit($this->limit); |
||||
$cursor->skip($this->offset); |
||||
return $cursor; |
||||
} |
||||
|
||||
/** |
||||
* Fetches rows from the given Mongo cursor. |
||||
* @param \MongoCursor $cursor Mongo cursor instance to fetch data from. |
||||
* @param boolean $all whether to fetch all rows or only first one. |
||||
* @param string|callable $indexBy the column name or PHP callback, |
||||
* by which the query results should be indexed by. |
||||
* @throws Exception on failure. |
||||
* @return array|boolean result. |
||||
*/ |
||||
protected function fetchRows($cursor, $all = true, $indexBy = null) |
||||
{ |
||||
$token = 'find(' . Json::encode($cursor->info()) . ')'; |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->fetchRowsInternal($cursor, $all, $indexBy); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param \MongoCursor $cursor Mongo cursor instance to fetch data from. |
||||
* @param boolean $all whether to fetch all rows or only first one. |
||||
* @param string|callable $indexBy value to index by. |
||||
* @return array|boolean result. |
||||
* @see Query::fetchRows() |
||||
*/ |
||||
protected function fetchRowsInternal($cursor, $all, $indexBy) |
||||
{ |
||||
$result = []; |
||||
if ($all) { |
||||
foreach ($cursor as $row) { |
||||
if ($indexBy !== null) { |
||||
if (is_string($indexBy)) { |
||||
$key = $row[$indexBy]; |
||||
} else { |
||||
$key = call_user_func($indexBy, $row); |
||||
} |
||||
$result[$key] = $row; |
||||
} else { |
||||
$result[] = $row; |
||||
} |
||||
} |
||||
} else { |
||||
if ($cursor->hasNext()) { |
||||
$result = $cursor->getNext(); |
||||
} else { |
||||
$result = false; |
||||
} |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Executes the query and returns all results as an array. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return array the query results. If the query results in nothing, an empty array will be returned. |
||||
*/ |
||||
public function all($db = null) |
||||
{ |
||||
$cursor = $this->buildCursor($db); |
||||
return $this->fetchRows($cursor, true, $this->indexBy); |
||||
} |
||||
|
||||
/** |
||||
* Executes the query and returns a single row of result. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query |
||||
* results in nothing. |
||||
*/ |
||||
public function one($db = null) |
||||
{ |
||||
$cursor = $this->buildCursor($db); |
||||
return $this->fetchRows($cursor, false); |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of records. |
||||
* @param string $q kept to match [[QueryInterface]], its value is ignored. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return integer number of records |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function count($q = '*', $db = null) |
||||
{ |
||||
$cursor = $this->buildCursor($db); |
||||
$token = 'find.count(' . Json::encode($cursor->info()) . ')'; |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $cursor->count(); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a value indicating whether the query result contains any row of data. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return boolean whether the query result contains any row of data. |
||||
*/ |
||||
public function exists($db = null) |
||||
{ |
||||
return $this->one($db) !== null; |
||||
} |
||||
|
||||
/** |
||||
* Returns the sum of the specified column values. |
||||
* @param string $q the column name. |
||||
* Make sure you properly quote column names in the expression. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return integer the sum of the specified column values |
||||
*/ |
||||
public function sum($q, $db = null) |
||||
{ |
||||
return $this->aggregate($q, 'sum', $db); |
||||
} |
||||
|
||||
/** |
||||
* Returns the average of the specified column values. |
||||
* @param string $q the column name. |
||||
* Make sure you properly quote column names in the expression. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return integer the average of the specified column values. |
||||
*/ |
||||
public function average($q, $db = null) |
||||
{ |
||||
return $this->aggregate($q, 'avg', $db); |
||||
} |
||||
|
||||
/** |
||||
* Returns the minimum of the specified column values. |
||||
* @param string $q the column name. |
||||
* Make sure you properly quote column names in the expression. |
||||
* @param Connection $db the database connection used to generate the SQL statement. |
||||
* If this parameter is not given, the `db` application component will be used. |
||||
* @return integer the minimum of the specified column values. |
||||
*/ |
||||
public function min($q, $db = null) |
||||
{ |
||||
return $this->aggregate($q, 'min', $db); |
||||
} |
||||
|
||||
/** |
||||
* Returns the maximum of the specified column values. |
||||
* @param string $q the column name. |
||||
* Make sure you properly quote column names in the expression. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return integer the maximum of the specified column values. |
||||
*/ |
||||
public function max($q, $db = null) |
||||
{ |
||||
return $this->aggregate($q, 'max', $db); |
||||
} |
||||
|
||||
/** |
||||
* Performs the aggregation for the given column. |
||||
* @param string $column column name. |
||||
* @param string $operator aggregation operator. |
||||
* @param Connection $db the database connection used to execute the query. |
||||
* @return integer aggregation result. |
||||
*/ |
||||
protected function aggregate($column, $operator, $db) |
||||
{ |
||||
$collection = $this->getCollection($db); |
||||
$pipelines = []; |
||||
if ($this->where !== null) { |
||||
$pipelines[] = ['$match' => $collection->buildCondition($this->where)]; |
||||
} |
||||
$pipelines[] = [ |
||||
'$group' => [ |
||||
'_id' => '1', |
||||
'total' => [ |
||||
'$' . $operator => '$' . $column |
||||
], |
||||
] |
||||
]; |
||||
$result = $collection->aggregate($pipelines); |
||||
if (array_key_exists(0, $result)) { |
||||
return $result[0]['total']; |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a list of distinct values for the given column across a collection. |
||||
* @param string $q column to use. |
||||
* @param Connection $db the Mongo connection used to execute the query. |
||||
* If this parameter is not given, the `mongo` application component will be used. |
||||
* @return array array of distinct values |
||||
*/ |
||||
public function distinct($q, $db = null) |
||||
{ |
||||
$collection = $this->getCollection($db); |
||||
if ($this->where !== null) { |
||||
$condition = $this->where; |
||||
} else { |
||||
$condition = []; |
||||
} |
||||
$result = $collection->distinct($q, $condition); |
||||
if ($result === false) { |
||||
return []; |
||||
} else { |
||||
return $result; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,116 @@
|
||||
Yii 2.0 Public Preview - MongoDb Extension |
||||
========================================== |
||||
|
||||
Thank you for choosing Yii - a high-performance component-based PHP framework. |
||||
|
||||
If you are looking for a production-ready PHP framework, please use |
||||
[Yii v1.1](https://github.com/yiisoft/yii). |
||||
|
||||
Yii 2.0 is still under heavy development. We may make significant changes |
||||
without prior notices. **Yii 2.0 is not ready for production use yet.** |
||||
|
||||
[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2) |
||||
|
||||
This is the yii2-sphinx extension. |
||||
|
||||
|
||||
Installation |
||||
------------ |
||||
|
||||
The preferred way to install this extension is through [composer](http://getcomposer.org/download/). |
||||
|
||||
Either run |
||||
``` |
||||
php composer.phar require yiisoft/yii2-mongo "*" |
||||
``` |
||||
|
||||
or add |
||||
``` |
||||
"yiisoft/yii2-mongo": "*" |
||||
``` |
||||
to the require section of your composer.json. |
||||
|
||||
|
||||
*Note: You might have to run `php composer.phar selfupdate`* |
||||
|
||||
|
||||
Usage & Documentation |
||||
--------------------- |
||||
|
||||
This extension adds [MongoDB](http://www.mongodb.org/) data storage support for the Yii2 framework. |
||||
|
||||
Note: extension requires [MongoDB PHP Extension](http://us1.php.net/manual/en/book.mongo.php) version 1.3.0 or higher. |
||||
|
||||
To use this extension, simply add the following code in your application configuration: |
||||
|
||||
```php |
||||
return [ |
||||
//.... |
||||
'components' => [ |
||||
'mongo' => [ |
||||
'class' => '\yii\mongo\Connection', |
||||
'dsn' => 'mongodb://developer:password@localhost:27017/mydatabase', |
||||
], |
||||
], |
||||
]; |
||||
``` |
||||
|
||||
This extension provides ActiveRecord solution similar ot the [[\yii\db\ActiveRecord]]. |
||||
To declare an ActiveRecord class you need to extend [[\yii\mongo\ActiveRecord]] and |
||||
implement the `collectionName` and 'attributes' methods: |
||||
|
||||
```php |
||||
use yii\mongo\ActiveRecord; |
||||
|
||||
class Customer extends ActiveRecord |
||||
{ |
||||
/** |
||||
* @return string the name of the index associated with this ActiveRecord class. |
||||
*/ |
||||
public static function collectionName() |
||||
{ |
||||
return 'customer'; |
||||
} |
||||
|
||||
/** |
||||
* @return array list of attribute names. |
||||
*/ |
||||
public function attributes() |
||||
{ |
||||
return ['name', 'email', 'address', 'status']; |
||||
} |
||||
} |
||||
``` |
||||
|
||||
You can use [[\yii\data\ActiveDataProvider]] with the [[\yii\mongo\Query]] and [[\yii\mongo\ActiveQuery]]: |
||||
|
||||
```php |
||||
use yii\data\ActiveDataProvider; |
||||
use yii\mongo\Query; |
||||
|
||||
$query = new Query; |
||||
$query->from('customer')->where(['status' => 2]); |
||||
$provider = new ActiveDataProvider([ |
||||
'query' => $query, |
||||
'pagination' => [ |
||||
'pageSize' => 10, |
||||
] |
||||
]); |
||||
$models = $provider->getModels(); |
||||
``` |
||||
|
||||
```php |
||||
use yii\data\ActiveDataProvider; |
||||
use app\models\Customer; |
||||
|
||||
$provider = new ActiveDataProvider([ |
||||
'query' => Customer::find(), |
||||
'pagination' => [ |
||||
'pageSize' => 10, |
||||
] |
||||
]); |
||||
$models = $provider->getModels(); |
||||
``` |
||||
|
||||
This extension supports [MongoGridFS](http://docs.mongodb.org/manual/core/gridfs/) via |
||||
classes at namespace "\yii\mongo\file". |
@ -0,0 +1,28 @@
|
||||
{ |
||||
"name": "yiisoft/yii2-mongo", |
||||
"description": "MongoDb extension for the Yii framework", |
||||
"keywords": ["yii", "mongo", "mongodb", "active-record"], |
||||
"type": "yii2-extension", |
||||
"license": "BSD-3-Clause", |
||||
"support": { |
||||
"issues": "https://github.com/yiisoft/yii2/issues?state=open", |
||||
"forum": "http://www.yiiframework.com/forum/", |
||||
"wiki": "http://www.yiiframework.com/wiki/", |
||||
"irc": "irc://irc.freenode.net/yii", |
||||
"source": "https://github.com/yiisoft/yii2" |
||||
}, |
||||
"authors": [ |
||||
{ |
||||
"name": "Paul Klimov", |
||||
"email": "klimov.paul@gmail.com" |
||||
} |
||||
], |
||||
"minimum-stability": "dev", |
||||
"require": { |
||||
"yiisoft/yii2": "*", |
||||
"ext-mongo": ">=1.3.0" |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { "yii\\mongo\\": "" } |
||||
} |
||||
} |
@ -0,0 +1,107 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo\file; |
||||
|
||||
use yii\db\ActiveQueryInterface; |
||||
use yii\db\ActiveQueryTrait; |
||||
|
||||
/** |
||||
* ActiveQuery represents a Mongo query associated with an file Active Record class. |
||||
* |
||||
* ActiveQuery instances are usually created by [[ActiveRecord::find()]]. |
||||
* |
||||
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], |
||||
* [[orderBy()]] to customize the query options. |
||||
* |
||||
* ActiveQuery also provides the following additional query options: |
||||
* |
||||
* - [[with()]]: list of relations that this query should be performed with. |
||||
* - [[asArray()]]: whether to return each record as an array. |
||||
* |
||||
* These options can be configured using methods of the same name. For example: |
||||
* |
||||
* ~~~ |
||||
* $images = ImageFile::find()->with('tags')->asArray()->all(); |
||||
* ~~~ |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class ActiveQuery extends Query implements ActiveQueryInterface |
||||
{ |
||||
use ActiveQueryTrait; |
||||
|
||||
/** |
||||
* Executes query and returns all results as an array. |
||||
* @param \yii\mongo\Connection $db the Mongo connection used to execute the query. |
||||
* If null, the Mongo connection returned by [[modelClass]] will be used. |
||||
* @return array the query results. If the query results in nothing, an empty array will be returned. |
||||
*/ |
||||
public function all($db = null) |
||||
{ |
||||
$cursor = $this->buildCursor($db); |
||||
$rows = $this->fetchRows($cursor); |
||||
if (!empty($rows)) { |
||||
$models = $this->createModels($rows); |
||||
if (!empty($this->with)) { |
||||
$this->findWith($this->with, $models); |
||||
} |
||||
return $models; |
||||
} else { |
||||
return []; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Executes query and returns a single row of result. |
||||
* @param \yii\mongo\Connection $db the Mongo connection used to execute the query. |
||||
* If null, the Mongo connection returned by [[modelClass]] will be used. |
||||
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], |
||||
* the query result may be either an array or an ActiveRecord object. Null will be returned |
||||
* if the query results in nothing. |
||||
*/ |
||||
public function one($db = null) |
||||
{ |
||||
$row = parent::one($db); |
||||
if ($row !== false) { |
||||
if ($this->asArray) { |
||||
$model = $row; |
||||
} else { |
||||
/** @var ActiveRecord $class */ |
||||
$class = $this->modelClass; |
||||
$model = $class::create($row); |
||||
} |
||||
if (!empty($this->with)) { |
||||
$models = [$model]; |
||||
$this->findWith($this->with, $models); |
||||
$model = $models[0]; |
||||
} |
||||
return $model; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo collection for this query. |
||||
* @param \yii\mongo\Connection $db Mongo connection. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public function getCollection($db = null) |
||||
{ |
||||
/** @var ActiveRecord $modelClass */ |
||||
$modelClass = $this->modelClass; |
||||
if ($db === null) { |
||||
$db = $modelClass::getDb(); |
||||
} |
||||
if ($this->from === null) { |
||||
$this->from = $modelClass::collectionName(); |
||||
} |
||||
return $db->getFileCollection($this->from); |
||||
} |
||||
} |
@ -0,0 +1,340 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo\file; |
||||
|
||||
use yii\base\InvalidParamException; |
||||
use yii\db\StaleObjectException; |
||||
use yii\web\UploadedFile; |
||||
|
||||
/** |
||||
* ActiveRecord is the base class for classes representing Mongo GridFS files in terms of objects. |
||||
* |
||||
* To specify source file use the [[file]] attribute. It can be specified in one of the following ways: |
||||
* - string - full name of the file, which content should be stored in GridFS |
||||
* - \yii\web\UploadedFile - uploaded file instance, which content should be stored in GridFS |
||||
* |
||||
* For example: |
||||
* |
||||
* ~~~ |
||||
* $record = new ImageFile(); |
||||
* $record->file = '/path/to/some/file.jpg'; |
||||
* $record->save(); |
||||
* ~~~ |
||||
* |
||||
* You can also specify file content via [[newFileContent]] attribute: |
||||
* |
||||
* ~~~ |
||||
* $record = new ImageFile(); |
||||
* $record->newFileContent = 'New file content'; |
||||
* $record->save(); |
||||
* ~~~ |
||||
* |
||||
* Note: [[newFileContent]] always takes precedence over [[file]]. |
||||
* |
||||
* @property \MongoId|string $_id primary key. |
||||
* @property string $filename name of stored file. |
||||
* @property \MongoDate $uploadDate file upload date. |
||||
* @property integer $length file size. |
||||
* @property integer $chunkSize file chunk size. |
||||
* @property string $md5 file md5 hash. |
||||
* @property \MongoGridFSFile|\yii\web\UploadedFile|string $file associated file. |
||||
* @property string $newFileContent new file content. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
abstract class ActiveRecord extends \yii\mongo\ActiveRecord |
||||
{ |
||||
/** |
||||
* Creates an [[ActiveQuery]] instance. |
||||
* This method is called by [[find()]] to start a "find" command. |
||||
* You may override this method to return a customized query (e.g. `ImageFileQuery` specified |
||||
* written for querying `ImageFile` purpose.) |
||||
* @return ActiveQuery the newly created [[ActiveQuery]] instance. |
||||
*/ |
||||
public static function createQuery() |
||||
{ |
||||
return new ActiveQuery(['modelClass' => get_called_class()]); |
||||
} |
||||
|
||||
/** |
||||
* Return the Mongo GridFS collection instance for this AR class. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public static function getCollection() |
||||
{ |
||||
return static::getDb()->getFileCollection(static::collectionName()); |
||||
} |
||||
|
||||
/** |
||||
* Creates an [[ActiveRelation]] instance. |
||||
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance. |
||||
* You may override this method to return a customized relation. |
||||
* @param array $config the configuration passed to the ActiveRelation class. |
||||
* @return ActiveRelation the newly created [[ActiveRelation]] instance. |
||||
*/ |
||||
public static function createActiveRelation($config = []) |
||||
{ |
||||
return new ActiveRelation($config); |
||||
} |
||||
|
||||
/** |
||||
* Returns the list of all attribute names of the model. |
||||
* This method could be overridden by child classes to define available attributes. |
||||
* Note: all attributes defined in base Active Record class should be always present |
||||
* in returned array. |
||||
* For example: |
||||
* ~~~ |
||||
* public function attributes() |
||||
* { |
||||
* return array_merge( |
||||
* parent::attributes(), |
||||
* ['tags', 'status'] |
||||
* ); |
||||
* } |
||||
* ~~~ |
||||
* @return array list of attribute names. |
||||
*/ |
||||
public function attributes() |
||||
{ |
||||
return [ |
||||
'_id', |
||||
'filename', |
||||
'uploadDate', |
||||
'length', |
||||
'chunkSize', |
||||
'md5', |
||||
'file', |
||||
'newFileContent' |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @see ActiveRecord::insert() |
||||
*/ |
||||
protected function insertInternal($attributes = null) |
||||
{ |
||||
if (!$this->beforeSave(true)) { |
||||
return false; |
||||
} |
||||
$values = $this->getDirtyAttributes($attributes); |
||||
if (empty($values)) { |
||||
$currentAttributes = $this->getAttributes(); |
||||
foreach ($this->primaryKey() as $key) { |
||||
$values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null; |
||||
} |
||||
} |
||||
$collection = static::getCollection(); |
||||
if (isset($values['newFileContent'])) { |
||||
$newFileContent = $values['newFileContent']; |
||||
unset($values['newFileContent']); |
||||
} |
||||
if (isset($values['file'])) { |
||||
$newFile = $values['file']; |
||||
unset($values['file']); |
||||
} |
||||
if (isset($newFileContent)) { |
||||
$newId = $collection->insertFileContent($newFileContent, $values); |
||||
} elseif (isset($newFile)) { |
||||
$fileName = $this->extractFileName($newFile); |
||||
$newId = $collection->insertFile($fileName, $values); |
||||
} else { |
||||
$newId = $collection->insert($values); |
||||
} |
||||
$this->setAttribute('_id', $newId); |
||||
foreach ($values as $name => $value) { |
||||
$this->setOldAttribute($name, $value); |
||||
} |
||||
$this->afterSave(true); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* @see ActiveRecord::update() |
||||
* @throws StaleObjectException |
||||
*/ |
||||
protected function updateInternal($attributes = null) |
||||
{ |
||||
if (!$this->beforeSave(false)) { |
||||
return false; |
||||
} |
||||
$values = $this->getDirtyAttributes($attributes); |
||||
if (empty($values)) { |
||||
$this->afterSave(false); |
||||
return 0; |
||||
} |
||||
|
||||
$collection = static::getCollection(); |
||||
if (isset($values['newFileContent'])) { |
||||
$newFileContent = $values['newFileContent']; |
||||
unset($values['newFileContent']); |
||||
} |
||||
if (isset($values['file'])) { |
||||
$newFile = $values['file']; |
||||
unset($values['file']); |
||||
} |
||||
if (isset($newFileContent) || isset($newFile)) { |
||||
$rows = $this->deleteInternal(); |
||||
$insertValues = $values; |
||||
$insertValues['_id'] = $this->getAttribute('_id'); |
||||
if (isset($newFileContent)) { |
||||
$collection->insertFileContent($newFileContent, $insertValues); |
||||
} else { |
||||
$fileName = $this->extractFileName($newFile); |
||||
$collection->insertFile($fileName, $insertValues); |
||||
} |
||||
$this->setAttribute('newFileContent', null); |
||||
$this->setAttribute('file', null); |
||||
} else { |
||||
$condition = $this->getOldPrimaryKey(true); |
||||
$lock = $this->optimisticLock(); |
||||
if ($lock !== null) { |
||||
if (!isset($values[$lock])) { |
||||
$values[$lock] = $this->$lock + 1; |
||||
} |
||||
$condition[$lock] = $this->$lock; |
||||
} |
||||
// We do not check the return value of update() because it's possible |
||||
// that it doesn't change anything and thus returns 0. |
||||
$rows = $collection->update($condition, $values); |
||||
if ($lock !== null && !$rows) { |
||||
throw new StaleObjectException('The object being updated is outdated.'); |
||||
} |
||||
} |
||||
|
||||
foreach ($values as $name => $value) { |
||||
$this->setOldAttribute($name, $this->getAttribute($name)); |
||||
} |
||||
$this->afterSave(false); |
||||
return $rows; |
||||
} |
||||
|
||||
/** |
||||
* Extracts filename from given raw file value. |
||||
* @param mixed $file raw file value. |
||||
* @return string file name. |
||||
* @throws \yii\base\InvalidParamException on invalid file value. |
||||
*/ |
||||
protected function extractFileName($file) |
||||
{ |
||||
if ($file instanceof UploadedFile) { |
||||
return $file->tempName; |
||||
} elseif (is_string($file)) { |
||||
if (file_exists($file)) { |
||||
return $file; |
||||
} else { |
||||
throw new InvalidParamException("File '{$file}' does not exist."); |
||||
} |
||||
} else { |
||||
throw new InvalidParamException('Unsupported type of "file" attribute.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Refreshes the [[file]] attribute from file collection, using current primary key. |
||||
* @return \MongoGridFSFile|null refreshed file value. |
||||
*/ |
||||
public function refreshFile() |
||||
{ |
||||
$mongoFile = $this->getCollection()->get($this->getPrimaryKey()); |
||||
$this->setAttribute('file', $mongoFile); |
||||
return $mongoFile; |
||||
} |
||||
|
||||
/** |
||||
* Returns the associated file content. |
||||
* @return null|string file content. |
||||
* @throws \yii\base\InvalidParamException on invalid file attribute value. |
||||
*/ |
||||
public function getFileContent() |
||||
{ |
||||
$file = $this->getAttribute('file'); |
||||
if (empty($file) && !$this->getIsNewRecord()) { |
||||
$file = $this->refreshFile(); |
||||
} |
||||
if (empty($file)) { |
||||
return null; |
||||
} elseif ($file instanceof \MongoGridFSFile) { |
||||
$fileSize = $file->getSize(); |
||||
if (empty($fileSize)) { |
||||
return null; |
||||
} else { |
||||
return $file->getBytes(); |
||||
} |
||||
} elseif ($file instanceof UploadedFile) { |
||||
return file_get_contents($file->tempName); |
||||
} elseif (is_string($file)) { |
||||
if (file_exists($file)) { |
||||
return file_get_contents($file); |
||||
} else { |
||||
throw new InvalidParamException("File '{$file}' does not exist."); |
||||
} |
||||
} else { |
||||
throw new InvalidParamException('Unsupported type of "file" attribute.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes the the internal file content into the given filename. |
||||
* @param string $filename full filename to be written. |
||||
* @return boolean whether the operation was successful. |
||||
* @throws \yii\base\InvalidParamException on invalid file attribute value. |
||||
*/ |
||||
public function writeFile($filename) |
||||
{ |
||||
$file = $this->getAttribute('file'); |
||||
if (empty($file) && !$this->getIsNewRecord()) { |
||||
$file = $this->refreshFile(); |
||||
} |
||||
if (empty($file)) { |
||||
throw new InvalidParamException('There is no file associated with this object.'); |
||||
} elseif ($file instanceof \MongoGridFSFile) { |
||||
return ($file->write($filename) == $file->getSize()); |
||||
} elseif ($file instanceof UploadedFile) { |
||||
return copy($file->tempName, $filename); |
||||
} elseif (is_string($file)) { |
||||
if (file_exists($file)) { |
||||
return copy($file, $filename); |
||||
} else { |
||||
throw new InvalidParamException("File '{$file}' does not exist."); |
||||
} |
||||
} else { |
||||
throw new InvalidParamException('Unsupported type of "file" attribute.'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This method returns a stream resource that can be used with all file functions in PHP, |
||||
* which deal with reading files. The contents of the file are pulled out of MongoDB on the fly, |
||||
* so that the whole file does not have to be loaded into memory first. |
||||
* @return resource file stream resource. |
||||
* @throws \yii\base\InvalidParamException on invalid file attribute value. |
||||
*/ |
||||
public function getFileResource() |
||||
{ |
||||
$file = $this->getAttribute('file'); |
||||
if (empty($file) && !$this->getIsNewRecord()) { |
||||
$file = $this->refreshFile(); |
||||
} |
||||
if (empty($file)) { |
||||
throw new InvalidParamException('There is no file associated with this object.'); |
||||
} elseif ($file instanceof \MongoGridFSFile) { |
||||
return $file->getResource(); |
||||
} elseif ($file instanceof UploadedFile) { |
||||
return fopen($file->tempName, 'r'); |
||||
} elseif (is_string($file)) { |
||||
if (file_exists($file)) { |
||||
return fopen($file, 'r'); |
||||
} else { |
||||
throw new InvalidParamException("File '{$file}' does not exist."); |
||||
} |
||||
} else { |
||||
throw new InvalidParamException('Unsupported type of "file" attribute.'); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo\file; |
||||
|
||||
use yii\db\ActiveRelationInterface; |
||||
use yii\db\ActiveRelationTrait; |
||||
|
||||
/** |
||||
* ActiveRelation represents a relation to Mongo GridFS Active Record class. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface |
||||
{ |
||||
use ActiveRelationTrait; |
||||
} |
@ -0,0 +1,186 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo\file; |
||||
|
||||
use yii\mongo\Exception; |
||||
use Yii; |
||||
|
||||
/** |
||||
* Collection represents the Mongo GridFS collection information. |
||||
* |
||||
* A file collection object is usually created by calling [[Database::getFileCollection()]] or [[Connection::getFileCollection()]]. |
||||
* |
||||
* File collection inherits all interface from regular [[\yii\mongo\Collection]], adding methods to store files. |
||||
* |
||||
* @property \yii\mongo\Collection $chunkCollection file chunks Mongo collection. This property is read-only. |
||||
* @method \MongoGridFSCursor find() returns a cursor for the search results. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Collection extends \yii\mongo\Collection |
||||
{ |
||||
/** |
||||
* @var \MongoGridFS Mongo GridFS collection instance. |
||||
*/ |
||||
public $mongoCollection; |
||||
/** |
||||
* @var \yii\mongo\Collection file chunks Mongo collection. |
||||
*/ |
||||
private $_chunkCollection; |
||||
|
||||
/** |
||||
* Returns the Mongo collection for the file chunks. |
||||
* @param boolean $refresh whether to reload the collection instance even if it is found in the cache. |
||||
* @return \yii\mongo\Collection mongo collection instance. |
||||
*/ |
||||
public function getChunkCollection($refresh = false) |
||||
{ |
||||
if ($refresh || !is_object($this->_chunkCollection)) { |
||||
$this->_chunkCollection = Yii::createObject([ |
||||
'class' => 'yii\mongo\Collection', |
||||
'mongoCollection' => $this->mongoCollection->chunks |
||||
]); |
||||
} |
||||
return $this->_chunkCollection; |
||||
} |
||||
|
||||
/** |
||||
* 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 = []) |
||||
{ |
||||
$result = parent::remove($condition, $options); |
||||
$this->tryLastError(); // MongoGridFS::remove will return even if the remove failed |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Creates new file in GridFS collection from given local filesystem file. |
||||
* Additional attributes can be added file document using $metadata. |
||||
* @param string $filename name of the file to store. |
||||
* @param array $metadata other metadata fields to include in the file document. |
||||
* @param array $options list of options in format: optionName => optionValue |
||||
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] |
||||
* unless an "_id" was explicitly specified in the metadata. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function insertFile($filename, $metadata = [], $options = []) |
||||
{ |
||||
$token = 'Inserting file into ' . $this->getFullName(); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$options = array_merge(['w' => 1], $options); |
||||
$result = $this->mongoCollection->storeFile($filename, $metadata, $options); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates new file in GridFS collection with specified content. |
||||
* Additional attributes can be added file document using $metadata. |
||||
* @param string $bytes string of bytes to store. |
||||
* @param array $metadata other metadata fields to include in the file document. |
||||
* @param array $options list of options in format: optionName => optionValue |
||||
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] |
||||
* unless an "_id" was explicitly specified in the metadata. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function insertFileContent($bytes, $metadata = [], $options = []) |
||||
{ |
||||
$token = 'Inserting file content into ' . $this->getFullName(); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$options = array_merge(['w' => 1], $options); |
||||
$result = $this->mongoCollection->storeBytes($bytes, $metadata, $options); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates new file in GridFS collection from uploaded file. |
||||
* Additional attributes can be added file document using $metadata. |
||||
* @param string $name name of the uploaded file to store. This should correspond to |
||||
* the file field's name attribute in the HTML form. |
||||
* @param array $metadata other metadata fields to include in the file document. |
||||
* @return mixed the "_id" of the saved file document. This will be a generated [[\MongoId]] |
||||
* unless an "_id" was explicitly specified in the metadata. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function insertUploads($name, $metadata = []) |
||||
{ |
||||
$token = 'Inserting file uploads into ' . $this->getFullName(); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->storeUpload($name, $metadata); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Retrieves the file with given _id. |
||||
* @param mixed $id _id of the file to find. |
||||
* @return \MongoGridFSFile|null found file, or null if file does not exist |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function get($id) |
||||
{ |
||||
$token = 'Inserting file uploads into ' . $this->getFullName(); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->get($id); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return $result; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Deletes the file with given _id. |
||||
* @param mixed $id _id of the file to find. |
||||
* @return boolean whether the operation was successful. |
||||
* @throws Exception on failure. |
||||
*/ |
||||
public function delete($id) |
||||
{ |
||||
$token = 'Inserting file uploads into ' . $this->getFullName(); |
||||
Yii::info($token, __METHOD__); |
||||
try { |
||||
Yii::beginProfile($token, __METHOD__); |
||||
$result = $this->mongoCollection->delete($id); |
||||
$this->tryResultError($result); |
||||
Yii::endProfile($token, __METHOD__); |
||||
return true; |
||||
} catch (\Exception $e) { |
||||
Yii::endProfile($token, __METHOD__); |
||||
throw new Exception($e->getMessage(), (int)$e->getCode(), $e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,75 @@
|
||||
<?php |
||||
/** |
||||
* @link http://www.yiiframework.com/ |
||||
* @copyright Copyright (c) 2008 Yii Software LLC |
||||
* @license http://www.yiiframework.com/license/ |
||||
*/ |
||||
|
||||
namespace yii\mongo\file; |
||||
|
||||
use Yii; |
||||
use yii\helpers\Json; |
||||
use yii\mongo\Exception; |
||||
|
||||
/** |
||||
* Query represents Mongo "find" operation for GridFS collection. |
||||
* |
||||
* Query behaves exactly as regular [[\yii\mongo\Query]]. |
||||
* Found files will be represented as arrays of file document attributes with |
||||
* additional 'file' key, which stores [[\MongoGridFSFile]] instance. |
||||
* |
||||
* @author Paul Klimov <klimov.paul@gmail.com> |
||||
* @since 2.0 |
||||
*/ |
||||
class Query extends \yii\mongo\Query |
||||
{ |
||||
/** |
||||
* Returns the Mongo collection for this query. |
||||
* @param \yii\mongo\Connection $db Mongo connection. |
||||
* @return Collection collection instance. |
||||
*/ |
||||
public function getCollection($db = null) |
||||
{ |
||||
if ($db === null) { |
||||
$db = Yii::$app->getComponent('mongo'); |
||||
} |
||||
return $db->getFileCollection($this->from); |
||||
} |
||||
|
||||
/** |
||||
* @param \MongoGridFSCursor $cursor Mongo cursor instance to fetch data from. |
||||
* @param boolean $all whether to fetch all rows or only first one. |
||||
* @param string|callable $indexBy value to index by. |
||||
* @return array|boolean result. |
||||
* @see Query::fetchRows() |
||||
*/ |
||||
protected function fetchRowsInternal($cursor, $all, $indexBy) |
||||
{ |
||||
$result = []; |
||||
if ($all) { |
||||
foreach ($cursor as $file) { |
||||
$row = $file->file; |
||||
$row['file'] = $file; |
||||
if ($indexBy !== null) { |
||||
if (is_string($indexBy)) { |
||||
$key = $row[$indexBy]; |
||||
} else { |
||||
$key = call_user_func($indexBy, $row); |
||||
} |
||||
$result[$key] = $row; |
||||
} else { |
||||
$result[] = $row; |
||||
} |
||||
} |
||||
} else { |
||||
if ($cursor->hasNext()) { |
||||
$file = $cursor->getNext(); |
||||
$result = $file->file; |
||||
$result['file'] = $file; |
||||
} else { |
||||
$result = false; |
||||
} |
||||
} |
||||
return $result; |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\data\ar\mongo; |
||||
|
||||
/** |
||||
* Test Mongo ActiveRecord |
||||
*/ |
||||
class ActiveRecord extends \yii\mongo\ActiveRecord |
||||
{ |
||||
public static $db; |
||||
|
||||
public static function getDb() |
||||
{ |
||||
return self::$db; |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\data\ar\mongo; |
||||
|
||||
class Customer extends ActiveRecord |
||||
{ |
||||
public static function collectionName() |
||||
{ |
||||
return 'customer'; |
||||
} |
||||
|
||||
public function attributes() |
||||
{ |
||||
return [ |
||||
'_id', |
||||
'name', |
||||
'email', |
||||
'address', |
||||
'status', |
||||
]; |
||||
} |
||||
|
||||
public static function activeOnly($query) |
||||
{ |
||||
$query->andWhere(['status' => 2]); |
||||
} |
||||
|
||||
public function getOrders() |
||||
{ |
||||
return $this->hasMany(CustomerOrder::className(), ['customer_id' => '_id']); |
||||
} |
||||
} |
@ -0,0 +1,27 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\data\ar\mongo; |
||||
|
||||
|
||||
class CustomerOrder extends ActiveRecord |
||||
{ |
||||
public static function collectionName() |
||||
{ |
||||
return 'customer_order'; |
||||
} |
||||
|
||||
public function attributes() |
||||
{ |
||||
return [ |
||||
'_id', |
||||
'number', |
||||
'customer_id', |
||||
'items', |
||||
]; |
||||
} |
||||
|
||||
public function getCustomer() |
||||
{ |
||||
return $this->hasOne(Customer::className(), ['_id' => 'customer_id']); |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\data\ar\mongo\file; |
||||
|
||||
/** |
||||
* Test Mongo ActiveRecord |
||||
*/ |
||||
class ActiveRecord extends \yii\mongo\file\ActiveRecord |
||||
{ |
||||
public static $db; |
||||
|
||||
public static function getDb() |
||||
{ |
||||
return self::$db; |
||||
} |
||||
} |
@ -0,0 +1,27 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\data\ar\mongo\file; |
||||
|
||||
class CustomerFile extends ActiveRecord |
||||
{ |
||||
public static function collectionName() |
||||
{ |
||||
return 'customer_fs'; |
||||
} |
||||
|
||||
public function attributes() |
||||
{ |
||||
return array_merge( |
||||
parent::attributes(), |
||||
[ |
||||
'tag', |
||||
'status', |
||||
] |
||||
); |
||||
} |
||||
|
||||
public static function activeOnly($query) |
||||
{ |
||||
$query->andWhere(['status' => 2]); |
||||
} |
||||
} |
@ -0,0 +1,91 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\data\ActiveDataProvider; |
||||
use yii\mongo\Query; |
||||
use yiiunit\data\ar\mongo\ActiveRecord; |
||||
use yiiunit\data\ar\mongo\Customer; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class ActiveDataProviderTest extends MongoTestCase |
||||
{ |
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
ActiveRecord::$db = $this->getConnection(); |
||||
$this->setUpTestRows(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection(Customer::collectionName()); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = []; |
||||
for ($i = 1; $i <= 10; $i++) { |
||||
$rows[] = [ |
||||
'name' => 'name' . $i, |
||||
'email' => 'email' . $i, |
||||
'address' => 'address' . $i, |
||||
'status' => $i, |
||||
]; |
||||
} |
||||
$collection->batchInsert($rows); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testQuery() |
||||
{ |
||||
$query = new Query; |
||||
$query->from('customer'); |
||||
|
||||
$provider = new ActiveDataProvider([ |
||||
'query' => $query, |
||||
'db' => $this->getConnection(), |
||||
]); |
||||
$models = $provider->getModels(); |
||||
$this->assertEquals(10, count($models)); |
||||
|
||||
$provider = new ActiveDataProvider([ |
||||
'query' => $query, |
||||
'db' => $this->getConnection(), |
||||
'pagination' => [ |
||||
'pageSize' => 5, |
||||
] |
||||
]); |
||||
$models = $provider->getModels(); |
||||
$this->assertEquals(5, count($models)); |
||||
} |
||||
|
||||
public function testActiveQuery() |
||||
{ |
||||
$provider = new ActiveDataProvider([ |
||||
'query' => Customer::find()->orderBy('id ASC'), |
||||
]); |
||||
$models = $provider->getModels(); |
||||
$this->assertEquals(10, count($models)); |
||||
$this->assertTrue($models[0] instanceof Customer); |
||||
$keys = $provider->getKeys(); |
||||
$this->assertTrue($keys[0] instanceof \MongoId); |
||||
|
||||
$provider = new ActiveDataProvider([ |
||||
'query' => Customer::find(), |
||||
'pagination' => [ |
||||
'pageSize' => 5, |
||||
] |
||||
]); |
||||
$models = $provider->getModels(); |
||||
$this->assertEquals(5, count($models)); |
||||
} |
||||
} |
@ -0,0 +1,246 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\mongo\ActiveQuery; |
||||
use yiiunit\data\ar\mongo\ActiveRecord; |
||||
use yiiunit\data\ar\mongo\Customer; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class ActiveRecordTest extends MongoTestCase |
||||
{ |
||||
/** |
||||
* @var array[] list of test rows. |
||||
*/ |
||||
protected $testRows = []; |
||||
|
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
ActiveRecord::$db = $this->getConnection(); |
||||
$this->setUpTestRows(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection(Customer::collectionName()); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = []; |
||||
for ($i = 1; $i <= 10; $i++) { |
||||
$rows[] = [ |
||||
'name' => 'name' . $i, |
||||
'email' => 'email' . $i, |
||||
'address' => 'address' . $i, |
||||
'status' => $i, |
||||
]; |
||||
} |
||||
$collection->batchInsert($rows); |
||||
$this->testRows = $rows; |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testFind() |
||||
{ |
||||
// find one |
||||
$result = Customer::find(); |
||||
$this->assertTrue($result instanceof ActiveQuery); |
||||
$customer = $result->one(); |
||||
$this->assertTrue($customer instanceof Customer); |
||||
|
||||
// find all |
||||
$customers = Customer::find()->all(); |
||||
$this->assertEquals(10, count($customers)); |
||||
$this->assertTrue($customers[0] instanceof Customer); |
||||
$this->assertTrue($customers[1] instanceof Customer); |
||||
|
||||
// find by _id |
||||
$testId = $this->testRows[0]['_id']; |
||||
$customer = Customer::find($testId); |
||||
$this->assertTrue($customer instanceof Customer); |
||||
$this->assertEquals($testId, $customer->_id); |
||||
|
||||
// find by column values |
||||
$customer = Customer::find(['name' => 'name5']); |
||||
$this->assertTrue($customer instanceof Customer); |
||||
$this->assertEquals($this->testRows[4]['_id'], $customer->_id); |
||||
$this->assertEquals('name5', $customer->name); |
||||
$customer = Customer::find(['name' => 'unexisting name']); |
||||
$this->assertNull($customer); |
||||
|
||||
// find by attributes |
||||
$customer = Customer::find()->where(['status' => 4])->one(); |
||||
$this->assertTrue($customer instanceof Customer); |
||||
$this->assertEquals(4, $customer->status); |
||||
|
||||
// find count, sum, average, min, max, distinct |
||||
$this->assertEquals(10, Customer::find()->count()); |
||||
$this->assertEquals(1, Customer::find()->where(['status' => 2])->count()); |
||||
$this->assertEquals((1+10)/2*10, Customer::find()->sum('status')); |
||||
$this->assertEquals((1+10)/2, Customer::find()->average('status')); |
||||
$this->assertEquals(1, Customer::find()->min('status')); |
||||
$this->assertEquals(10, Customer::find()->max('status')); |
||||
$this->assertEquals(range(1, 10), Customer::find()->distinct('status')); |
||||
|
||||
// scope |
||||
$this->assertEquals(1, Customer::find()->activeOnly()->count()); |
||||
|
||||
// asArray |
||||
$testRow = $this->testRows[2]; |
||||
$customer = Customer::find()->where(['_id' => $testRow['_id']])->asArray()->one(); |
||||
$this->assertEquals($testRow, $customer); |
||||
|
||||
// indexBy |
||||
$customers = Customer::find()->indexBy('name')->all(); |
||||
$this->assertTrue($customers['name1'] instanceof Customer); |
||||
$this->assertTrue($customers['name2'] instanceof Customer); |
||||
|
||||
// indexBy callable |
||||
$customers = Customer::find()->indexBy(function ($customer) { |
||||
return $customer->status . '-' . $customer->status; |
||||
})->all(); |
||||
$this->assertTrue($customers['1-1'] instanceof Customer); |
||||
$this->assertTrue($customers['2-2'] instanceof Customer); |
||||
} |
||||
|
||||
public function testInsert() |
||||
{ |
||||
$record = new Customer; |
||||
$record->name = 'new name'; |
||||
$record->email = 'new email'; |
||||
$record->address = 'new address'; |
||||
$record->status = 7; |
||||
|
||||
$this->assertTrue($record->isNewRecord); |
||||
|
||||
$record->save(); |
||||
|
||||
$this->assertTrue($record->_id instanceof \MongoId); |
||||
$this->assertFalse($record->isNewRecord); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
*/ |
||||
public function testUpdate() |
||||
{ |
||||
$record = new Customer; |
||||
$record->name = 'new name'; |
||||
$record->email = 'new email'; |
||||
$record->address = 'new address'; |
||||
$record->status = 7; |
||||
$record->save(); |
||||
|
||||
// save |
||||
$record = Customer::find($record->_id); |
||||
$this->assertTrue($record instanceof Customer); |
||||
$this->assertEquals(7, $record->status); |
||||
$this->assertFalse($record->isNewRecord); |
||||
|
||||
$record->status = 9; |
||||
$record->save(); |
||||
$this->assertEquals(9, $record->status); |
||||
$this->assertFalse($record->isNewRecord); |
||||
$record2 = Customer::find($record->_id); |
||||
$this->assertEquals(9, $record2->status); |
||||
|
||||
// updateAll |
||||
$pk = ['_id' => $record->_id]; |
||||
$ret = Customer::updateAll(['status' => 55], $pk); |
||||
$this->assertEquals(1, $ret); |
||||
$record = Customer::find($pk); |
||||
$this->assertEquals(55, $record->status); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
*/ |
||||
public function testDelete() |
||||
{ |
||||
// delete |
||||
$record = new Customer; |
||||
$record->name = 'new name'; |
||||
$record->email = 'new email'; |
||||
$record->address = 'new address'; |
||||
$record->status = 7; |
||||
$record->save(); |
||||
|
||||
$record = Customer::find($record->_id); |
||||
$record->delete(); |
||||
$record = Customer::find($record->_id); |
||||
$this->assertNull($record); |
||||
|
||||
// deleteAll |
||||
$record = new Customer; |
||||
$record->name = 'new name'; |
||||
$record->email = 'new email'; |
||||
$record->address = 'new address'; |
||||
$record->status = 7; |
||||
$record->save(); |
||||
|
||||
$ret = Customer::deleteAll(['name' => 'new name']); |
||||
$this->assertEquals(1, $ret); |
||||
$records = Customer::find()->where(['name' => 'new name'])->all(); |
||||
$this->assertEquals(0, count($records)); |
||||
} |
||||
|
||||
public function testUpdateAllCounters() |
||||
{ |
||||
$this->assertEquals(1, Customer::updateAllCounters(['status' => 10], ['status' => 10])); |
||||
|
||||
$record = Customer::find(['status' => 10]); |
||||
$this->assertNull($record); |
||||
} |
||||
|
||||
/** |
||||
* @depends testUpdateAllCounters |
||||
*/ |
||||
public function testUpdateCounters() |
||||
{ |
||||
$record = Customer::find($this->testRows[9]); |
||||
|
||||
$originalCounter = $record->status; |
||||
$counterIncrement = 20; |
||||
$record->updateCounters(['status' => $counterIncrement]); |
||||
$this->assertEquals($originalCounter + $counterIncrement, $record->status); |
||||
|
||||
$refreshedRecord = Customer::find($record->_id); |
||||
$this->assertEquals($originalCounter + $counterIncrement, $refreshedRecord->status); |
||||
} |
||||
|
||||
/** |
||||
* @depends testUpdate |
||||
*/ |
||||
public function testUpdateNestedAttribute() |
||||
{ |
||||
$record = new Customer; |
||||
$record->name = 'new name'; |
||||
$record->email = 'new email'; |
||||
$record->address = [ |
||||
'city' => 'SomeCity', |
||||
'street' => 'SomeStreet', |
||||
]; |
||||
$record->status = 7; |
||||
$record->save(); |
||||
|
||||
// save |
||||
$record = Customer::find($record->_id); |
||||
$newAddress = [ |
||||
'city' => 'AnotherCity' |
||||
]; |
||||
$record->address = $newAddress; |
||||
$record->save(); |
||||
$record2 = Customer::find($record->_id); |
||||
$this->assertEquals($newAddress, $record2->address); |
||||
} |
||||
} |
@ -0,0 +1,83 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yiiunit\data\ar\mongo\ActiveRecord; |
||||
use yiiunit\data\ar\mongo\Customer; |
||||
use yiiunit\data\ar\mongo\CustomerOrder; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class ActiveRelationTest extends MongoTestCase |
||||
{ |
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
ActiveRecord::$db = $this->getConnection(); |
||||
$this->setUpTestRows(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection(Customer::collectionName()); |
||||
$this->dropCollection(CustomerOrder::collectionName()); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$customerCollection = $this->getConnection()->getCollection('customer'); |
||||
|
||||
$customers = []; |
||||
for ($i = 1; $i <= 5; $i++) { |
||||
$customers[] = [ |
||||
'name' => 'name' . $i, |
||||
'email' => 'email' . $i, |
||||
'address' => 'address' . $i, |
||||
'status' => $i, |
||||
]; |
||||
} |
||||
$customerCollection->batchInsert($customers); |
||||
|
||||
$customerOrderCollection = $this->getConnection()->getCollection('customer_order'); |
||||
$customerOrders = []; |
||||
foreach ($customers as $customer) { |
||||
$customerOrders[] = [ |
||||
'customer_id' => $customer['_id'], |
||||
'number' => $customer['status'], |
||||
]; |
||||
$customerOrders[] = [ |
||||
'customer_id' => $customer['_id'], |
||||
'number' => $customer['status'] + 1, |
||||
]; |
||||
} |
||||
$customerOrderCollection->batchInsert($customerOrders); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testFindLazy() |
||||
{ |
||||
/** @var CustomerOrder $order */ |
||||
$order = CustomerOrder::find(['number' => 2]); |
||||
$this->assertFalse($order->isRelationPopulated('customer')); |
||||
$index = $order->customer; |
||||
$this->assertTrue($order->isRelationPopulated('customer')); |
||||
$this->assertTrue($index instanceof Customer); |
||||
$this->assertEquals(1, count($order->populatedRelations)); |
||||
} |
||||
|
||||
public function testFindEager() |
||||
{ |
||||
$orders = CustomerOrder::find()->with('customer')->all(); |
||||
$this->assertEquals(10, count($orders)); |
||||
$this->assertTrue($orders[0]->isRelationPopulated('customer')); |
||||
$this->assertTrue($orders[1]->isRelationPopulated('customer')); |
||||
$this->assertTrue($orders[0]->customer instanceof Customer); |
||||
$this->assertTrue($orders[1]->customer instanceof Customer); |
||||
} |
||||
} |
@ -0,0 +1,313 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class CollectionTest extends MongoTestCase |
||||
{ |
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection('customer'); |
||||
$this->dropCollection('mapReduceOut'); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testGetName() |
||||
{ |
||||
$collectionName = 'customer'; |
||||
$collection = $this->getConnection()->getCollection($collectionName); |
||||
$this->assertEquals($collectionName, $collection->getName()); |
||||
$this->assertEquals($this->mongoConfig['defaultDatabaseName'] . '.' . $collectionName, $collection->getFullName()); |
||||
} |
||||
|
||||
public function testFind() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$cursor = $collection->find(); |
||||
$this->assertTrue($cursor instanceof \MongoCursor); |
||||
} |
||||
|
||||
public function testInsert() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$id = $collection->insert($data); |
||||
$this->assertTrue($id instanceof \MongoId); |
||||
$this->assertNotEmpty($id->__toString()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
* @depends testFind |
||||
*/ |
||||
public function testFindAll() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$id = $collection->insert($data); |
||||
|
||||
$cursor = $collection->find(); |
||||
$rows = []; |
||||
foreach ($cursor as $row) { |
||||
$rows[] = $row; |
||||
} |
||||
$this->assertEquals(1, count($rows)); |
||||
$this->assertEquals($id, $rows[0]['_id']); |
||||
} |
||||
|
||||
/** |
||||
* @depends testFind |
||||
*/ |
||||
public function testBatchInsert() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = [ |
||||
[ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
], |
||||
[ |
||||
'name' => 'customer 2', |
||||
'address' => 'customer 2 address', |
||||
], |
||||
]; |
||||
$insertedRows = $collection->batchInsert($rows); |
||||
$this->assertTrue($insertedRows[0]['_id'] instanceof \MongoId); |
||||
$this->assertTrue($insertedRows[1]['_id'] instanceof \MongoId); |
||||
$this->assertEquals(count($rows), $collection->find()->count()); |
||||
} |
||||
|
||||
public function testSave() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$id = $collection->save($data); |
||||
$this->assertTrue($id instanceof \MongoId); |
||||
$this->assertNotEmpty($id->__toString()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testSave |
||||
*/ |
||||
public function testUpdateBySave() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$newId = $collection->save($data); |
||||
|
||||
$updatedId = $collection->save($data); |
||||
$this->assertEquals($newId, $updatedId, 'Unable to update data!'); |
||||
|
||||
$data['_id'] = $newId->__toString(); |
||||
$updatedId = $collection->save($data); |
||||
$this->assertEquals($newId, $updatedId, 'Unable to updated data by string id!'); |
||||
} |
||||
|
||||
/** |
||||
* @depends testFindAll |
||||
*/ |
||||
public function testRemove() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$id = $collection->insert($data); |
||||
|
||||
$count = $collection->remove(['_id' => $id]); |
||||
$this->assertEquals(1, $count); |
||||
|
||||
$rows = $this->findAll($collection); |
||||
$this->assertEquals(0, count($rows)); |
||||
} |
||||
|
||||
/** |
||||
* @depends testFindAll |
||||
*/ |
||||
public function testUpdate() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$data = [ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
]; |
||||
$id = $collection->insert($data); |
||||
|
||||
$newData = [ |
||||
'name' => 'new name' |
||||
]; |
||||
$count = $collection->update(['_id' => $id], $newData); |
||||
$this->assertEquals(1, $count); |
||||
|
||||
list($row) = $this->findAll($collection); |
||||
$this->assertEquals($newData['name'], $row['name']); |
||||
} |
||||
|
||||
/** |
||||
* @depends testBatchInsert |
||||
*/ |
||||
public function testGroup() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = [ |
||||
[ |
||||
'name' => 'customer 1', |
||||
'address' => 'customer 1 address', |
||||
], |
||||
[ |
||||
'name' => 'customer 2', |
||||
'address' => 'customer 2 address', |
||||
], |
||||
]; |
||||
$collection->batchInsert($rows); |
||||
|
||||
$keys = ['address' => 1]; |
||||
$initial = ['items' => []]; |
||||
$reduce = "function (obj, prev) { prev.items.push(obj.name); }"; |
||||
$result = $collection->group($keys, $initial, $reduce); |
||||
$this->assertEquals(2, count($result)); |
||||
$this->assertNotEmpty($result[0]['address']); |
||||
$this->assertNotEmpty($result[0]['items']); |
||||
} |
||||
|
||||
/** |
||||
* @depends testBatchInsert |
||||
*/ |
||||
public function testMapReduce() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = [ |
||||
[ |
||||
'name' => 'customer 1', |
||||
'status' => 1, |
||||
'amount' => 100, |
||||
], |
||||
[ |
||||
'name' => 'customer 2', |
||||
'status' => 1, |
||||
'amount' => 200, |
||||
], |
||||
[ |
||||
'name' => 'customer 2', |
||||
'status' => 2, |
||||
'amount' => 400, |
||||
], |
||||
[ |
||||
'name' => 'customer 2', |
||||
'status' => 3, |
||||
'amount' => 500, |
||||
], |
||||
]; |
||||
$collection->batchInsert($rows); |
||||
|
||||
$result = $collection->mapReduce( |
||||
'function () {emit(this.status, this.amount)}', |
||||
'function (key, values) {return Array.sum(values)}', |
||||
'mapReduceOut', |
||||
['status' => ['$lt' => 3]] |
||||
); |
||||
$this->assertEquals('mapReduceOut', $result); |
||||
|
||||
$outputCollection = $this->getConnection()->getCollection($result); |
||||
$rows = $this->findAll($outputCollection); |
||||
$expectedRows = [ |
||||
[ |
||||
'_id' => 1, |
||||
'value' => 300, |
||||
], |
||||
[ |
||||
'_id' => 2, |
||||
'value' => 400, |
||||
], |
||||
]; |
||||
$this->assertEquals($expectedRows, $rows); |
||||
} |
||||
|
||||
public function testCreateIndex() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$columns = [ |
||||
'name', |
||||
'status' => \MongoCollection::DESCENDING, |
||||
]; |
||||
$this->assertTrue($collection->createIndex($columns)); |
||||
$indexInfo = $collection->mongoCollection->getIndexInfo(); |
||||
$this->assertEquals(2, count($indexInfo)); |
||||
} |
||||
|
||||
/** |
||||
* @depends testCreateIndex |
||||
*/ |
||||
public function testDropIndex() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
|
||||
$collection->createIndex('name'); |
||||
$this->assertTrue($collection->dropIndex('name')); |
||||
$indexInfo = $collection->mongoCollection->getIndexInfo(); |
||||
$this->assertEquals(1, count($indexInfo)); |
||||
|
||||
$this->setExpectedException('\yii\mongo\Exception'); |
||||
$collection->dropIndex('name'); |
||||
} |
||||
|
||||
/** |
||||
* @depends testCreateIndex |
||||
*/ |
||||
public function testDropAllIndexes() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$collection->createIndex('name'); |
||||
$this->assertEquals(2, $collection->dropAllIndexes()); |
||||
$indexInfo = $collection->mongoCollection->getIndexInfo(); |
||||
$this->assertEquals(1, count($indexInfo)); |
||||
} |
||||
|
||||
/** |
||||
* @depends testBatchInsert |
||||
* @depends testCreateIndex |
||||
*/ |
||||
public function testFullTextSearch() |
||||
{ |
||||
if (version_compare('2.4', $this->getServerVersion(), '>')) { |
||||
$this->markTestSkipped("Mongo Server 2.4 required."); |
||||
} |
||||
|
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
|
||||
$rows = [ |
||||
[ |
||||
'name' => 'customer 1', |
||||
'status' => 1, |
||||
'amount' => 100, |
||||
], |
||||
[ |
||||
'name' => 'some customer', |
||||
'status' => 1, |
||||
'amount' => 200, |
||||
], |
||||
]; |
||||
$collection->batchInsert($rows); |
||||
$collection->createIndex(['name' => 'text']); |
||||
|
||||
$result = $collection->fullTextSearch('some'); |
||||
$this->assertNotEmpty($result); |
||||
} |
||||
} |
@ -0,0 +1,119 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\mongo\Collection; |
||||
use yii\mongo\file\Collection as FileCollection; |
||||
use yii\mongo\Connection; |
||||
use yii\mongo\Database; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class ConnectionTest extends MongoTestCase |
||||
{ |
||||
public function testConstruct() |
||||
{ |
||||
$connection = $this->getConnection(false); |
||||
$params = $this->mongoConfig; |
||||
|
||||
$connection->open(); |
||||
|
||||
$this->assertEquals($params['dsn'], $connection->dsn); |
||||
$this->assertEquals($params['defaultDatabaseName'], $connection->defaultDatabaseName); |
||||
$this->assertEquals($params['options'], $connection->options); |
||||
} |
||||
|
||||
public function testOpenClose() |
||||
{ |
||||
$connection = $this->getConnection(false, false); |
||||
|
||||
$this->assertFalse($connection->isActive); |
||||
$this->assertEquals(null, $connection->mongoClient); |
||||
|
||||
$connection->open(); |
||||
$this->assertTrue($connection->isActive); |
||||
$this->assertTrue(is_object($connection->mongoClient)); |
||||
|
||||
$connection->close(); |
||||
$this->assertFalse($connection->isActive); |
||||
$this->assertEquals(null, $connection->mongoClient); |
||||
|
||||
$connection = new Connection; |
||||
$connection->dsn = 'unknown::memory:'; |
||||
$this->setExpectedException('yii\mongo\Exception'); |
||||
$connection->open(); |
||||
} |
||||
|
||||
public function testGetDatabase() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
|
||||
$database = $connection->getDatabase($connection->defaultDatabaseName); |
||||
$this->assertTrue($database instanceof Database); |
||||
$this->assertTrue($database->mongoDb instanceof \MongoDB); |
||||
|
||||
$database2 = $connection->getDatabase($connection->defaultDatabaseName); |
||||
$this->assertTrue($database === $database2); |
||||
|
||||
$databaseRefreshed = $connection->getDatabase($connection->defaultDatabaseName, true); |
||||
$this->assertFalse($database === $databaseRefreshed); |
||||
} |
||||
|
||||
/** |
||||
* @depends testGetDatabase |
||||
*/ |
||||
public function testGetDefaultDatabase() |
||||
{ |
||||
$connection = new Connection(); |
||||
$connection->dsn = $this->mongoConfig['dsn']; |
||||
$connection->defaultDatabaseName = $this->mongoConfig['defaultDatabaseName']; |
||||
$database = $connection->getDatabase(); |
||||
$this->assertTrue($database instanceof Database, 'Unable to get default database!'); |
||||
|
||||
$connection = new Connection(); |
||||
$connection->dsn = $this->mongoConfig['dsn']; |
||||
$connection->options = ['db' => $this->mongoConfig['defaultDatabaseName']]; |
||||
$database = $connection->getDatabase(); |
||||
$this->assertTrue($database instanceof Database, 'Unable to determine default database from options!'); |
||||
|
||||
$connection = new Connection(); |
||||
$connection->dsn = $this->mongoConfig['dsn'] . '/' . $this->mongoConfig['defaultDatabaseName']; |
||||
$database = $connection->getDatabase(); |
||||
$this->assertTrue($database instanceof Database, 'Unable to determine default database from dsn!'); |
||||
} |
||||
|
||||
/** |
||||
* @depends testGetDefaultDatabase |
||||
*/ |
||||
public function testGetCollection() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
|
||||
$collection = $connection->getCollection('customer'); |
||||
$this->assertTrue($collection instanceof Collection); |
||||
|
||||
$collection2 = $connection->getCollection('customer'); |
||||
$this->assertTrue($collection === $collection2); |
||||
|
||||
$collection2 = $connection->getCollection('customer', true); |
||||
$this->assertFalse($collection === $collection2); |
||||
} |
||||
|
||||
/** |
||||
* @depends testGetDefaultDatabase |
||||
*/ |
||||
public function testGetFileCollection() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
|
||||
$collection = $connection->getFileCollection('testfs'); |
||||
$this->assertTrue($collection instanceof FileCollection); |
||||
|
||||
$collection2 = $connection->getFileCollection('testfs'); |
||||
$this->assertTrue($collection === $collection2); |
||||
|
||||
$collection2 = $connection->getFileCollection('testfs', true); |
||||
$this->assertFalse($collection === $collection2); |
||||
} |
||||
} |
@ -0,0 +1,70 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\mongo\Collection; |
||||
use yii\mongo\file\Collection as FileCollection; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class DatabaseTest extends MongoTestCase |
||||
{ |
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection('customer'); |
||||
$this->dropFileCollection('testfs'); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testGetCollection() |
||||
{ |
||||
$database = $connection = $this->getConnection()->getDatabase(); |
||||
|
||||
$collection = $database->getCollection('customer'); |
||||
$this->assertTrue($collection instanceof Collection); |
||||
$this->assertTrue($collection->mongoCollection instanceof \MongoCollection); |
||||
|
||||
$collection2 = $database->getCollection('customer'); |
||||
$this->assertTrue($collection === $collection2); |
||||
|
||||
$collectionRefreshed = $database->getCollection('customer', true); |
||||
$this->assertFalse($collection === $collectionRefreshed); |
||||
} |
||||
|
||||
public function testGetFileCollection() |
||||
{ |
||||
$database = $connection = $this->getConnection()->getDatabase(); |
||||
|
||||
$collection = $database->getFileCollection('testfs'); |
||||
$this->assertTrue($collection instanceof FileCollection); |
||||
$this->assertTrue($collection->mongoCollection instanceof \MongoGridFS); |
||||
|
||||
$collection2 = $database->getFileCollection('testfs'); |
||||
$this->assertTrue($collection === $collection2); |
||||
|
||||
$collectionRefreshed = $database->getFileCollection('testfs', true); |
||||
$this->assertFalse($collection === $collectionRefreshed); |
||||
} |
||||
|
||||
public function testExecuteCommand() |
||||
{ |
||||
$database = $connection = $this->getConnection()->getDatabase(); |
||||
|
||||
$result = $database->executeCommand([ |
||||
'distinct' => 'customer', |
||||
'key' => 'name' |
||||
]); |
||||
$this->assertTrue(array_key_exists('ok', $result)); |
||||
$this->assertTrue(array_key_exists('values', $result)); |
||||
} |
||||
|
||||
public function testCreateCollection() |
||||
{ |
||||
$database = $connection = $this->getConnection()->getDatabase(); |
||||
$collection = $database->createCollection('customer'); |
||||
$this->assertTrue($collection instanceof \MongoCollection); |
||||
} |
||||
} |
@ -0,0 +1,149 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\helpers\FileHelper; |
||||
use yii\mongo\Connection; |
||||
use Yii; |
||||
use yii\mongo\Exception; |
||||
use yiiunit\TestCase; |
||||
|
||||
class MongoTestCase extends TestCase |
||||
{ |
||||
/** |
||||
* @var array Mongo connection configuration. |
||||
*/ |
||||
protected $mongoConfig = [ |
||||
'dsn' => 'mongodb://localhost:27017', |
||||
'defaultDatabaseName' => 'yii2test', |
||||
'options' => [], |
||||
]; |
||||
/** |
||||
* @var Connection Mongo connection instance. |
||||
*/ |
||||
protected $mongo; |
||||
|
||||
public static function setUpBeforeClass() |
||||
{ |
||||
static::loadClassMap(); |
||||
} |
||||
|
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
if (!extension_loaded('mongo')) { |
||||
$this->markTestSkipped('mongo extension required.'); |
||||
} |
||||
$config = $this->getParam('mongo'); |
||||
if (!empty($config)) { |
||||
$this->mongoConfig = $config; |
||||
} |
||||
$this->mockApplication(); |
||||
static::loadClassMap(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
if ($this->mongo) { |
||||
$this->mongo->close(); |
||||
} |
||||
$this->destroyApplication(); |
||||
} |
||||
|
||||
/** |
||||
* Adds sphinx extension files to [[Yii::$classPath]], |
||||
* avoiding the necessity of usage Composer autoloader. |
||||
*/ |
||||
protected static function loadClassMap() |
||||
{ |
||||
$baseNameSpace = 'yii/mongo'; |
||||
$basePath = realpath(__DIR__. '/../../../../extensions/mongo'); |
||||
$files = FileHelper::findFiles($basePath); |
||||
foreach ($files as $file) { |
||||
$classRelativePath = str_replace($basePath, '', $file); |
||||
$classFullName = str_replace(['/', '.php'], ['\\', ''], $baseNameSpace . $classRelativePath); |
||||
Yii::$classMap[$classFullName] = $file; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param boolean $reset whether to clean up the test database |
||||
* @param boolean $open whether to open test database |
||||
* @return \yii\mongo\Connection |
||||
*/ |
||||
public function getConnection($reset = false, $open = true) |
||||
{ |
||||
if (!$reset && $this->mongo) { |
||||
return $this->mongo; |
||||
} |
||||
$db = new Connection; |
||||
$db->dsn = $this->mongoConfig['dsn']; |
||||
$db->defaultDatabaseName = $this->mongoConfig['defaultDatabaseName']; |
||||
if (isset($this->mongoConfig['options'])) { |
||||
$db->options = $this->mongoConfig['options']; |
||||
} |
||||
if ($open) { |
||||
$db->open(); |
||||
} |
||||
$this->mongo = $db; |
||||
return $db; |
||||
} |
||||
|
||||
/** |
||||
* Drops the specified collection. |
||||
* @param string $name collection name. |
||||
*/ |
||||
protected function dropCollection($name) |
||||
{ |
||||
if ($this->mongo) { |
||||
try { |
||||
$this->mongo->getCollection($name)->drop(); |
||||
} catch (Exception $e) { |
||||
// shut down exception |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Drops the specified file collection. |
||||
* @param string $name file collection name. |
||||
*/ |
||||
protected function dropFileCollection($name = 'fs') |
||||
{ |
||||
if ($this->mongo) { |
||||
try { |
||||
$this->mongo->getFileCollection($name)->drop(); |
||||
} catch (Exception $e) { |
||||
// shut down exception |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Finds all records in collection. |
||||
* @param \yii\mongo\Collection $collection |
||||
* @param array $condition |
||||
* @param array $fields |
||||
* @return array rows |
||||
*/ |
||||
protected function findAll($collection, $condition = [], $fields = []) |
||||
{ |
||||
$cursor = $collection->find($condition, $fields); |
||||
$result = []; |
||||
foreach ($cursor as $data) { |
||||
$result[] = $data; |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Returns the Mongo server version. |
||||
* @return string Mongo server version. |
||||
*/ |
||||
protected function getServerVersion() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$buildInfo = $connection->getDatabase()->executeCommand(['buildinfo' => true]); |
||||
return $buildInfo['version']; |
||||
} |
||||
} |
@ -0,0 +1,132 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\mongo\Query; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class QueryRunTest extends MongoTestCase |
||||
{ |
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
$this->setUpTestRows(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$this->dropCollection('customer'); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$collection = $this->getConnection()->getCollection('customer'); |
||||
$rows = []; |
||||
for ($i = 1; $i <= 10; $i++) { |
||||
$rows[] = [ |
||||
'name' => 'name' . $i, |
||||
'address' => 'address' . $i, |
||||
'avatar' => [ |
||||
'width' => 50 + $i, |
||||
'height' => 100 + $i, |
||||
'url' => 'http://some.url/' . $i, |
||||
], |
||||
]; |
||||
} |
||||
$collection->batchInsert($rows); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testAll() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('customer')->all($connection); |
||||
$this->assertEquals(10, count($rows)); |
||||
} |
||||
|
||||
public function testDirectMatch() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->where(['name' => 'name1']) |
||||
->all($connection); |
||||
$this->assertEquals(1, count($rows)); |
||||
$this->assertEquals('name1', $rows[0]['name']); |
||||
} |
||||
|
||||
public function testIndexBy() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->indexBy('name') |
||||
->all($connection); |
||||
$this->assertEquals(10, count($rows)); |
||||
$this->assertNotEmpty($rows['name1']); |
||||
} |
||||
|
||||
public function testInCondition() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->where([ |
||||
'name' => ['name1', 'name5'] |
||||
]) |
||||
->all($connection); |
||||
$this->assertEquals(2, count($rows)); |
||||
$this->assertEquals('name1', $rows[0]['name']); |
||||
$this->assertEquals('name5', $rows[1]['name']); |
||||
} |
||||
|
||||
public function testOrCondition() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->where(['name' => 'name1']) |
||||
->orWhere(['address' => 'address5']) |
||||
->all($connection); |
||||
$this->assertEquals(2, count($rows)); |
||||
$this->assertEquals('name1', $rows[0]['name']); |
||||
$this->assertEquals('address5', $rows[1]['address']); |
||||
} |
||||
|
||||
public function testOrder() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
|
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->orderBy(['name' => SORT_DESC]) |
||||
->all($connection); |
||||
$this->assertEquals('name9', $rows[0]['name']); |
||||
|
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->orderBy(['avatar.height' => SORT_DESC]) |
||||
->all($connection); |
||||
$this->assertEquals('name10', $rows[0]['name']); |
||||
} |
||||
|
||||
public function testMatchPlainId() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$row = $query->from('customer')->one($connection); |
||||
$query = new Query; |
||||
$rows = $query->from('customer') |
||||
->where(['_id' => $row['_id']->__toString()]) |
||||
->all($connection); |
||||
$this->assertEquals(1, count($rows)); |
||||
} |
||||
} |
@ -0,0 +1,97 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo; |
||||
|
||||
use yii\mongo\Query; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class QueryTest extends MongoTestCase |
||||
{ |
||||
public function testSelect() |
||||
{ |
||||
// default |
||||
$query = new Query; |
||||
$select = []; |
||||
$query->select($select); |
||||
$this->assertEquals($select, $query->select); |
||||
|
||||
$query = new Query; |
||||
$select = ['name', 'something']; |
||||
$query->select($select); |
||||
$this->assertEquals($select, $query->select); |
||||
} |
||||
|
||||
public function testFrom() |
||||
{ |
||||
$query = new Query; |
||||
$from = 'customer'; |
||||
$query->from($from); |
||||
$this->assertEquals($from, $query->from); |
||||
|
||||
$query = new Query; |
||||
$from = ['', 'customer']; |
||||
$query->from($from); |
||||
$this->assertEquals($from, $query->from); |
||||
} |
||||
|
||||
public function testWhere() |
||||
{ |
||||
$query = new Query; |
||||
$query->where(['name' => 'name1']); |
||||
$this->assertEquals(['name' => 'name1'], $query->where); |
||||
|
||||
$query->andWhere(['address' => 'address1']); |
||||
$this->assertEquals( |
||||
[ |
||||
'and', |
||||
['name' => 'name1'], |
||||
['address' => 'address1'] |
||||
], |
||||
$query->where |
||||
); |
||||
|
||||
$query->orWhere(['name' => 'name2']); |
||||
$this->assertEquals( |
||||
[ |
||||
'or', |
||||
[ |
||||
'and', |
||||
['name' => 'name1'], |
||||
['address' => 'address1'] |
||||
], |
||||
['name' => 'name2'] |
||||
|
||||
], |
||||
$query->where |
||||
); |
||||
} |
||||
|
||||
public function testOrder() |
||||
{ |
||||
$query = new Query; |
||||
$query->orderBy('team'); |
||||
$this->assertEquals(['team' => SORT_ASC], $query->orderBy); |
||||
|
||||
$query->addOrderBy('company'); |
||||
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC], $query->orderBy); |
||||
|
||||
$query->addOrderBy('age'); |
||||
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_ASC], $query->orderBy); |
||||
|
||||
$query->addOrderBy(['age' => SORT_DESC]); |
||||
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_ASC, 'age' => SORT_DESC], $query->orderBy); |
||||
|
||||
$query->addOrderBy('age ASC, company DESC'); |
||||
$this->assertEquals(['team' => SORT_ASC, 'company' => SORT_DESC, 'age' => SORT_ASC], $query->orderBy); |
||||
} |
||||
|
||||
public function testLimitOffset() |
||||
{ |
||||
$query = new Query; |
||||
$query->limit(10)->offset(5); |
||||
$this->assertEquals(10, $query->limit); |
||||
$this->assertEquals(5, $query->offset); |
||||
} |
||||
} |
@ -0,0 +1,323 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo\file; |
||||
|
||||
use Yii; |
||||
use yii\helpers\FileHelper; |
||||
use yiiunit\extensions\mongo\MongoTestCase; |
||||
use yii\mongo\file\ActiveQuery; |
||||
use yiiunit\data\ar\mongo\file\ActiveRecord; |
||||
use yiiunit\data\ar\mongo\file\CustomerFile; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class ActiveRecordTest extends MongoTestCase |
||||
{ |
||||
/** |
||||
* @var array[] list of test rows. |
||||
*/ |
||||
protected $testRows = []; |
||||
|
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
ActiveRecord::$db = $this->getConnection(); |
||||
$this->setUpTestRows(); |
||||
$filePath = $this->getTestFilePath(); |
||||
if (!file_exists($filePath)) { |
||||
FileHelper::createDirectory($filePath); |
||||
} |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$filePath = $this->getTestFilePath(); |
||||
if (file_exists($filePath)) { |
||||
FileHelper::removeDirectory($filePath); |
||||
} |
||||
$this->dropFileCollection(CustomerFile::collectionName()); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* @return string test file path. |
||||
*/ |
||||
protected function getTestFilePath() |
||||
{ |
||||
return Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . basename(get_class($this)) . '_' . getmypid(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(CustomerFile::collectionName()); |
||||
$rows = []; |
||||
for ($i = 1; $i <= 10; $i++) { |
||||
$record = [ |
||||
'tag' => 'tag' . $i, |
||||
'status' => $i, |
||||
]; |
||||
$content = 'content' . $i; |
||||
$record['_id'] = $collection->insertFileContent($content, $record); |
||||
$record['content'] = $content; |
||||
$rows[] = $record; |
||||
} |
||||
$this->testRows = $rows; |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testFind() |
||||
{ |
||||
// find one |
||||
$result = CustomerFile::find(); |
||||
$this->assertTrue($result instanceof ActiveQuery); |
||||
$customer = $result->one(); |
||||
$this->assertTrue($customer instanceof CustomerFile); |
||||
|
||||
// find all |
||||
$customers = CustomerFile::find()->all(); |
||||
$this->assertEquals(10, count($customers)); |
||||
$this->assertTrue($customers[0] instanceof CustomerFile); |
||||
$this->assertTrue($customers[1] instanceof CustomerFile); |
||||
|
||||
// find by _id |
||||
$testId = $this->testRows[0]['_id']; |
||||
$customer = CustomerFile::find($testId); |
||||
$this->assertTrue($customer instanceof CustomerFile); |
||||
$this->assertEquals($testId, $customer->_id); |
||||
|
||||
// find by column values |
||||
$customer = CustomerFile::find(['tag' => 'tag5']); |
||||
$this->assertTrue($customer instanceof CustomerFile); |
||||
$this->assertEquals($this->testRows[4]['_id'], $customer->_id); |
||||
$this->assertEquals('tag5', $customer->tag); |
||||
$customer = CustomerFile::find(['tag' => 'unexisting tag']); |
||||
$this->assertNull($customer); |
||||
|
||||
// find by attributes |
||||
$customer = CustomerFile::find()->where(['status' => 4])->one(); |
||||
$this->assertTrue($customer instanceof CustomerFile); |
||||
$this->assertEquals(4, $customer->status); |
||||
|
||||
// find count, sum, average, min, max, distinct |
||||
$this->assertEquals(10, CustomerFile::find()->count()); |
||||
$this->assertEquals(1, CustomerFile::find()->where(['status' => 2])->count()); |
||||
$this->assertEquals((1+10)/2*10, CustomerFile::find()->sum('status')); |
||||
$this->assertEquals((1+10)/2, CustomerFile::find()->average('status')); |
||||
$this->assertEquals(1, CustomerFile::find()->min('status')); |
||||
$this->assertEquals(10, CustomerFile::find()->max('status')); |
||||
$this->assertEquals(range(1, 10), CustomerFile::find()->distinct('status')); |
||||
|
||||
// scope |
||||
$this->assertEquals(1, CustomerFile::find()->activeOnly()->count()); |
||||
|
||||
// asArray |
||||
$testRow = $this->testRows[2]; |
||||
$customer = CustomerFile::find()->where(['_id' => $testRow['_id']])->asArray()->one(); |
||||
$this->assertEquals($testRow['_id'], $customer['_id']); |
||||
$this->assertEquals($testRow['tag'], $customer['tag']); |
||||
$this->assertEquals($testRow['status'], $customer['status']); |
||||
|
||||
// indexBy |
||||
$customers = CustomerFile::find()->indexBy('tag')->all(); |
||||
$this->assertTrue($customers['tag1'] instanceof CustomerFile); |
||||
$this->assertTrue($customers['tag2'] instanceof CustomerFile); |
||||
|
||||
// indexBy callable |
||||
$customers = CustomerFile::find()->indexBy(function ($customer) { |
||||
return $customer->status . '-' . $customer->status; |
||||
})->all(); |
||||
$this->assertTrue($customers['1-1'] instanceof CustomerFile); |
||||
$this->assertTrue($customers['2-2'] instanceof CustomerFile); |
||||
} |
||||
|
||||
public function testInsert() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
|
||||
$this->assertTrue($record->isNewRecord); |
||||
|
||||
$record->save(); |
||||
|
||||
$this->assertTrue($record->_id instanceof \MongoId); |
||||
$this->assertFalse($record->isNewRecord); |
||||
|
||||
$fileContent = $record->getFileContent(); |
||||
$this->assertEmpty($fileContent); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
*/ |
||||
public function testInsertFile() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
|
||||
$fileName = __FILE__; |
||||
$record->setAttribute('file', $fileName); |
||||
|
||||
$record->save(); |
||||
|
||||
$this->assertTrue($record->_id instanceof \MongoId); |
||||
$this->assertFalse($record->isNewRecord); |
||||
|
||||
$fileContent = $record->getFileContent(); |
||||
$this->assertEquals(file_get_contents($fileName), $fileContent); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
*/ |
||||
public function testInsertFileContent() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
|
||||
$newFileContent = 'Test new file content'; |
||||
$record->setAttribute('newFileContent', $newFileContent); |
||||
|
||||
$record->save(); |
||||
|
||||
$this->assertTrue($record->_id instanceof \MongoId); |
||||
$this->assertFalse($record->isNewRecord); |
||||
|
||||
$fileContent = $record->getFileContent(); |
||||
$this->assertEquals($newFileContent, $fileContent); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsert |
||||
*/ |
||||
public function testUpdate() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
$record->save(); |
||||
|
||||
// save |
||||
$record = CustomerFile::find($record->_id); |
||||
$this->assertTrue($record instanceof CustomerFile); |
||||
$this->assertEquals(7, $record->status); |
||||
$this->assertFalse($record->isNewRecord); |
||||
|
||||
$record->status = 9; |
||||
$record->save(); |
||||
$this->assertEquals(9, $record->status); |
||||
$this->assertFalse($record->isNewRecord); |
||||
$record2 = CustomerFile::find($record->_id); |
||||
$this->assertEquals(9, $record2->status); |
||||
|
||||
// updateAll |
||||
$pk = ['_id' => $record->_id]; |
||||
$ret = CustomerFile::updateAll(['status' => 55], $pk); |
||||
$this->assertEquals(1, $ret); |
||||
$record = CustomerFile::find($pk); |
||||
$this->assertEquals(55, $record->status); |
||||
} |
||||
|
||||
/** |
||||
* @depends testUpdate |
||||
* @depends testInsertFileContent |
||||
*/ |
||||
public function testUpdateFile() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
$newFileContent = 'Test new file content'; |
||||
$record->setAttribute('newFileContent', $newFileContent); |
||||
$record->save(); |
||||
|
||||
$updateFileName = __FILE__; |
||||
$record = CustomerFile::find($record->_id); |
||||
$record->setAttribute('file', $updateFileName); |
||||
$record->status = 55; |
||||
$record->save(); |
||||
$this->assertEquals(file_get_contents($updateFileName), $record->getFileContent()); |
||||
|
||||
$record2 = CustomerFile::find($record->_id); |
||||
$this->assertEquals($record->status, $record2->status); |
||||
$this->assertEquals(file_get_contents($updateFileName), $record2->getFileContent()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testUpdate |
||||
* @depends testInsertFileContent |
||||
*/ |
||||
public function testUpdateFileContent() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
$newFileContent = 'Test new file content'; |
||||
$record->setAttribute('newFileContent', $newFileContent); |
||||
$record->save(); |
||||
|
||||
$updateFileContent = 'New updated file content'; |
||||
$record = CustomerFile::find($record->_id); |
||||
$record->setAttribute('newFileContent', $updateFileContent); |
||||
$record->status = 55; |
||||
$record->save(); |
||||
$this->assertEquals($updateFileContent, $record->getFileContent()); |
||||
|
||||
$record2 = CustomerFile::find($record->_id); |
||||
$this->assertEquals($record->status, $record2->status); |
||||
$this->assertEquals($updateFileContent, $record2->getFileContent()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsertFileContent |
||||
*/ |
||||
public function testWriteFile() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
$newFileContent = 'Test new file content'; |
||||
$record->setAttribute('newFileContent', $newFileContent); |
||||
$record->save(); |
||||
|
||||
$outputFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . 'out.txt'; |
||||
$this->assertTrue($record->writeFile($outputFileName)); |
||||
$this->assertEquals($newFileContent, file_get_contents($outputFileName)); |
||||
|
||||
$record2 = CustomerFile::find($record->_id); |
||||
$outputFileName = $this->getTestFilePath() . DIRECTORY_SEPARATOR . 'out_refreshed.txt'; |
||||
$this->assertTrue($record2->writeFile($outputFileName)); |
||||
$this->assertEquals($newFileContent, file_get_contents($outputFileName)); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsertFileContent |
||||
*/ |
||||
public function testGetFileResource() |
||||
{ |
||||
$record = new CustomerFile; |
||||
$record->tag = 'new new'; |
||||
$record->status = 7; |
||||
$newFileContent = 'Test new file content'; |
||||
$record->setAttribute('newFileContent', $newFileContent); |
||||
$record->save(); |
||||
|
||||
$fileResource = $record->getFileResource(); |
||||
$contents = stream_get_contents($fileResource); |
||||
fclose($fileResource); |
||||
$this->assertEquals($newFileContent, $contents); |
||||
|
||||
$record2 = CustomerFile::find($record->_id); |
||||
$fileResource = $record2->getFileResource(); |
||||
$contents = stream_get_contents($fileResource); |
||||
fclose($fileResource); |
||||
$this->assertEquals($newFileContent, $contents); |
||||
} |
||||
} |
@ -0,0 +1,98 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo\file; |
||||
|
||||
use yiiunit\extensions\mongo\MongoTestCase; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class CollectionTest extends MongoTestCase |
||||
{ |
||||
protected function tearDown() |
||||
{ |
||||
$this->dropFileCollection('fs'); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testGetChunkCollection() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
$chunkCollection = $collection->getChunkCollection(); |
||||
$this->assertTrue($chunkCollection instanceof \yii\mongo\Collection); |
||||
$this->assertTrue($chunkCollection->mongoCollection instanceof \MongoCollection); |
||||
} |
||||
|
||||
public function testFind() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
$cursor = $collection->find(); |
||||
$this->assertTrue($cursor instanceof \MongoGridFSCursor); |
||||
} |
||||
|
||||
public function testInsertFile() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
|
||||
$filename = __FILE__; |
||||
$id = $collection->insertFile($filename); |
||||
$this->assertTrue($id instanceof \MongoId); |
||||
|
||||
$files = $this->findAll($collection); |
||||
$this->assertEquals(1, count($files)); |
||||
|
||||
/** @var $file \MongoGridFSFile */ |
||||
$file = $files[0]; |
||||
$this->assertEquals($filename, $file->getFilename()); |
||||
$this->assertEquals(file_get_contents($filename), $file->getBytes()); |
||||
} |
||||
|
||||
public function testInsertFileContent() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
|
||||
$bytes = 'Test file content'; |
||||
$id = $collection->insertFileContent($bytes); |
||||
$this->assertTrue($id instanceof \MongoId); |
||||
|
||||
$files = $this->findAll($collection); |
||||
$this->assertEquals(1, count($files)); |
||||
|
||||
/** @var $file \MongoGridFSFile */ |
||||
$file = $files[0]; |
||||
$this->assertEquals($bytes, $file->getBytes()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testInsertFileContent |
||||
*/ |
||||
public function testGet() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
|
||||
$bytes = 'Test file content'; |
||||
$id = $collection->insertFileContent($bytes); |
||||
|
||||
$file = $collection->get($id); |
||||
$this->assertTrue($file instanceof \MongoGridFSFile); |
||||
$this->assertEquals($bytes, $file->getBytes()); |
||||
} |
||||
|
||||
/** |
||||
* @depends testGet |
||||
*/ |
||||
public function testDelete() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
|
||||
$bytes = 'Test file content'; |
||||
$id = $collection->insertFileContent($bytes); |
||||
|
||||
$this->assertTrue($collection->delete($id)); |
||||
|
||||
$file = $collection->get($id); |
||||
$this->assertNull($file); |
||||
} |
||||
} |
@ -0,0 +1,70 @@
|
||||
<?php |
||||
|
||||
namespace yiiunit\extensions\mongo\file; |
||||
|
||||
use yii\mongo\file\Query; |
||||
use yiiunit\extensions\mongo\MongoTestCase; |
||||
|
||||
/** |
||||
* @group mongo |
||||
*/ |
||||
class QueryTest extends MongoTestCase |
||||
{ |
||||
protected function setUp() |
||||
{ |
||||
parent::setUp(); |
||||
$this->setUpTestRows(); |
||||
} |
||||
|
||||
protected function tearDown() |
||||
{ |
||||
$this->dropFileCollection(); |
||||
parent::tearDown(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up test rows. |
||||
*/ |
||||
protected function setUpTestRows() |
||||
{ |
||||
$collection = $this->getConnection()->getFileCollection(); |
||||
for ($i = 1; $i <= 10; $i++) { |
||||
$collection->insertFileContent('content' . $i, [ |
||||
'filename' => 'name' . $i, |
||||
'file_index' => $i, |
||||
]); |
||||
} |
||||
} |
||||
|
||||
// Tests : |
||||
|
||||
public function testAll() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('fs')->all($connection); |
||||
$this->assertEquals(10, count($rows)); |
||||
} |
||||
|
||||
public function testOne() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$row = $query->from('fs')->one($connection); |
||||
$this->assertTrue(is_array($row)); |
||||
$this->assertTrue($row['file'] instanceof \MongoGridFSFile); |
||||
} |
||||
|
||||
public function testDirectMatch() |
||||
{ |
||||
$connection = $this->getConnection(); |
||||
$query = new Query; |
||||
$rows = $query->from('fs') |
||||
->where(['file_index' => 5]) |
||||
->all($connection); |
||||
$this->assertEquals(1, count($rows)); |
||||
/** @var $file \MongoGridFSFile */ |
||||
$file = $rows[0]; |
||||
$this->assertEquals('name5', $file['filename']); |
||||
} |
||||
} |
Loading…
Reference in new issue