Skip to content
1343 lines (1219 sloc) 40.3 KB
<?php
/**
* EMongoDocument.php
*
* PHP version 5.2+
*
* @author Dariusz Górecki <darek.krk@gmail.com>
* @author Invenzzia Group, open-source division of CleverIT company http://www.invenzzia.org
* @copyright 2011 CleverIT http://www.cleverit.com.pl
* @license http://www.yiiframework.com/license/ BSD license
* @version 1.3
* @category ext
* @package ext.YiiMongoDbSuite
* @since v1.0
*/
/**
* EMongoDocument
*
* @property-read MongoDB $db
* @since v1.0
*/
abstract class EMongoDocument extends EMongoEmbeddedDocument
{
private $_new = false; // whether this instance is new or not
private $_criteria = null; // query criteria (used by finder only)
/**
* Static array that holds mongo collection object instances,
* protected access since v1.3
*
* @var array $_collections static array of loaded collection objects
* @since v1.3
*/
protected static $_collections = array(); // MongoCollection object
private static $_models = array();
private static $_indexes = array(); // Hold collection indexes array
private $_fsyncFlag = null; // Object level FSync flag
private $_safeFlag = null; // Object level Safe flag
protected $useCursor = null; // Whatever to return cursor instead on raw array
/**
* @var boolean $ensureIndexes whatever to check and create non existing indexes of collection
* @since v1.1
*/
protected $ensureIndexes = true; // Whatever to ensure indexes
/**
* EMongoDB component static instance
* @var EMongoDB $_emongoDb;
* @since v1.0
*/
protected static $_emongoDb;
/**
* MongoDB special field, every document has to have this
*
* @var mixed $_id
* @since v1.0
*/
public $_id;
/**
* Add scopes functionality
* @see CComponent::__call()
* @since v1.0
*/
public function __call($name, $parameters)
{
$scopes=$this->scopes();
if(isset($scopes[$name]))
{
$this->getDbCriteria()->mergeWith($scopes[$name]);
return $this;
}
return parent::__call($name,$parameters);
}
/**
* Constructor {@see setScenario()}
*
* @param string $scenario
* @since v1.0
*/
public function __construct($scenario='insert')
{
if($scenario==null) // internally used by populateRecord() and model()
return;
$this->setScenario($scenario);
$this->setIsNewRecord(true);
$this->init();
$this->attachBehaviors($this->behaviors());
$this->afterConstruct();
$this->initEmbeddedDocuments();
}
/**
* Return the primary key field for this collection, defaults to '_id'
* @return string|array field name, or array of fields for composite primary key
* @since v1.2.2
*/
public function primaryKey()
{
return '_id';
}
/**
* @since v1.2.2
*/
public function getPrimaryKey()
{
$pk = $this->primaryKey();
if(is_string($pk))
return $this->{$pk};
else
{
$return = array();
foreach($pk as $pkFiled)
$return[] = $this->{$pkFiled};
return $return;
}
}
/**
* Get EMongoDB component instance
* By default it is mongodb application component
*
* @return EMongoDB
* @since v1.0
*/
public function getMongoDBComponent()
{
if(self::$_emongoDb===null)
self::$_emongoDb = Yii::app()->getComponent('mongodb');
return self::$_emongoDb;
}
/**
* Set EMongoDB component instance
* @param EMongoDB $component
* @since v1.0
*/
public function setMongoDBComponent(EMongoDB $component)
{
self::$_emongoDb = $component;
}
/**
* Get raw MongoDB instance
* @return MongoDB
* @since v1.0
*/
public function getDb()
{
return $this->getMongoDBComponent()->getDbInstance();
}
/**
* This method must return collection name for use with this model
* this must be implemented in child classes
*
* this is read-only defined only at class define
* if you whant to set different colection during run-time
* use {@see setCollection()}
*
* @return string collection name
* @since v1.0
*/
abstract public function getCollectionName();
/**
* Returns current MongoCollection object
* By default this method use {@see getCollectionName()}
* @return MongoCollection
* @since v1.0
*/
public function getCollection()
{
if(!isset(self::$_collections[$this->getCollectionName()]))
self::$_collections[$this->getCollectionName()] = $this->getDb()->selectCollection($this->getCollectionName());
return self::$_collections[$this->getCollectionName()];
}
/**
* Set current MongoCollection object
* @param MongoCollection $collection
* @since v1.0
*/
public function setCollection(MongoCollection $collection)
{
self::$_collections[$this->getCollectionName()] = $collection;
}
/**
* Returns if the current record is new.
* @return boolean whether the record is new and should be inserted when calling {@link save}.
* This property is automatically set in constructor and {@link populateRecord}.
* Defaults to false, but it will be set to true if the instance is created using
* the new operator.
* @since v1.0
*/
public function getIsNewRecord()
{
return $this->_new;
}
/**
* Sets if the record is new.
* @param boolean $value whether the record is new and should be inserted when calling {@link save}.
* @see getIsNewRecord
* @since v1.0
*/
public function setIsNewRecord($value)
{
$this->_new=$value;
}
/**
* Returns the mongo criteria associated with this model.
* @param boolean $createIfNull whether to create a criteria instance if it does not exist. Defaults to true.
* @return EMongoCriteria the query criteria that is associated with this model.
* This criteria is mainly used by {@link scopes named scope} feature to accumulate
* different criteria specifications.
* @since v1.0
*/
public function getDbCriteria($createIfNull=true)
{
if($this->_criteria===null)
if(($c = $this->defaultScope()) !== array() || $createIfNull)
$this->_criteria = new EMongoCriteria($c);
return $this->_criteria;
}
/**
* Set girrent object, this will override proevious criteria
*
* @param EMongoCriteria $criteria
* @since v1.0
*/
public function setDbCriteria($criteria)
{
if(is_array($criteria))
$this->_criteria = new EMongoCriteria($criteria);
else if($criteria instanceof EMongoCriteria)
$this->_criteria = $criteria;
else
$this->_criteria = new EMongoCriteria();
}
/**
* Get FSync flag
*
* It will return the nearest not null value in order:
* - Object level
* - Model level
* - Glopal level (always set)
* @return boolean
*/
public function getFsyncFlag()
{
if($this->_fsyncFlag !== null)
return $this->_fsyncFlag; // We have flag set, return it
if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->_fsyncFlag !== null))
return self::$_models[get_class($this)]->_fsyncFlag; // Model have flag set, return it
return $this->getMongoDBComponent()->fsyncFlag;
}
/**
* Set object level FSync flag
* @param boolean $flag true|false value for FSync flag
*/
public function setFsyncFlag($flag)
{
$this->_fsyncFlag = ($flag == true);
if($this->_fsyncFlag)
$this->setSafeFlag(true); // Setting FSync flag to true will implicit set safe to true
}
/**
* Get Safe flag
*
* It will return the nearest not null value in order:
* - Object level
* - Model level
* - Glopal level (always set)
* @return boolean
*/
public function getSafeFlag()
{
if($this->_safeFlag !== null)
return $this->_safeFlag; // We have flag set, return it
if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->_safeFlag !== null))
return self::$_models[get_class($this)]->_safeFlag; // Model have flag set, return it
return $this->getMongoDBComponent()->safeFlag;
}
/**
* Set object level Safe flag
* @param boolean $flag true|false value for Safe flag
*/
public function setSafeFlag($flag)
{
$this->_safeFlag = ($flag == true);
}
/**
* Get value of use cursor flag
*
* It will return the nearest not null value in order:
* - Object level
* - Model level
* - Glopal level (always set)
* @return boolean
*/
public function getUseCursor()
{
if($this->useCursor !== null)
return $this->useCursor; // We have flag set, return it
if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->useCursor !== null))
return self::$_models[get_class($this)]->useCursor; // Model have flag set, return it
return $this->getMongoDBComponent()->useCursor;
}
/**
* Set object level value of use cursor flag
* @param boolean $useCursor true|false value for use cursor flag
*/
public function setUseCursor($useCursor)
{
$this->useCursor = ($useCursor == true);
}
/**
* Sets the attribute values in a massive way.
* @param array $values attribute values (name=>value) to be set.
* @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
* A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
* @see getSafeAttributeNames
* @see attributeNames
* @since v1.3.1
*/
public function setAttributes($values, $safeOnly=true)
{
if(!is_array($values))
return;
if($this->hasEmbeddedDocuments())
{
$attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
foreach($this->embeddedDocuments() as $fieldName => $className)
if(isset($values[$fieldName]) && isset($attributes[$fieldName]))
{
$this->$fieldName->setAttributes($values[$fieldName], $safeOnly);
unset($values[$fieldName]);
}
}
parent::setAttributes($values, $safeOnly);
}
/**
* This function check indexes and applies them to the collection if needed
* see CModel::init()
*
* @see EMongoEmbeddedDocument::init()
* @since v1.1
*/
public function init()
{
parent::init();
if($this->ensureIndexes && !isset(self::$_indexes[$this->getCollectionName()]))
{
$indexInfo = $this->getCollection()->getIndexInfo();
array_shift($indexInfo); // strip out default _id index
$indexes = array();
foreach($indexInfo as $index)
{
$indexes[$index['name']] = array(
'key'=>$index['key'],
'unique'=>isset($index['unique']) ? $index['unique'] : false,
);
}
self::$_indexes[$this->getCollectionName()] = $indexes;
$this->ensureIndexes();
}
}
/**
* This function may return array of indexes for this collection
* array sytntax is:
* return array(
* 'index_name'=>array('key'=>array('fieldName1'=>EMongoCriteria::SORT_ASC, 'fieldName2'=>EMongoCriteria::SORT_DESC),
* 'index2_name'=>array('key'=>array('fieldName3'=>EMongoCriteria::SORT_ASC, 'unique'=>true),
* );
* @return array list of indexes for this collection
* @since v1.1
*/
public function indexes()
{
return array();
}
/**
* @since v1.1
*/
private function ensureIndexes()
{
$indexNames = array_keys(self::$_indexes[$this->getCollectionName()]);
foreach($this->indexes() as $name=>$index)
{
if(!in_array($name, $indexNames))
{
if(version_compare(Mongo::VERSION, '1.0.2','>=') === true)
{
$this->getCollection()->ensureIndex(
$index['key'],
array('unique'=>isset($index['unique']) ? $index['unique'] : false, 'name'=>$name)
);
} else {
$this->getCollection()->ensureIndex(
$index['key'],
isset($index['unique']) ? $index['unique'] : false
);
}
self::$_indexes[$this->getCollectionName()][$name] = $index;
}
}
}
/**
* Returns the declaration of named scopes.
* A named scope represents a query criteria that can be chained together with
* other named scopes and applied to a query. This method should be overridden
* by child classes to declare named scopes for the particular document classes.
* For example, the following code declares two named scopes: 'recently' and
* 'published'.
* <pre>
* return array(
* 'published'=>array(
* 'conditions'=>array(
* 'status'=>array('==', 1),
* ),
* ),
* 'recently'=>array(
* 'sort'=>array('create_time'=>EMongoCriteria::SORT_DESC),
* 'limit'=>5,
* ),
* );
* </pre>
* If the above scopes are declared in a 'Post' model, we can perform the following
* queries:
* <pre>
* $posts=Post::model()->published()->findAll();
* $posts=Post::model()->published()->recently()->findAll();
* $posts=Post::model()->published()->published()->recently()->find();
* </pre>
*
* @return array the scope definition. The array keys are scope names; the array
* values are the corresponding scope definitions. Each scope definition is represented
* as an array whose keys must be properties of {@link EMongoCriteria}.
* @since v1.0
*/
public function scopes()
{
return array();
}
/**
* Returns the default named scope that should be implicitly applied to all queries for this model.
* Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.
* The default implementation simply returns an empty array. You may override this method
* if the model needs to be queried with some default criteria (e.g. only active records should be returned).
* @return array the mongo criteria. This will be used as the parameter to the constructor
* of {@link EMongoCriteria}.
* @since v1.2.2
*/
public function defaultScope()
{
return array();
}
/**
* Resets all scopes and criterias applied including default scope.
*
* @return EMongoDocument
* @since v1.0
*/
public function resetScope()
{
$this->_criteria = new EMongoCriteria();
return $this;
}
/**
* Applies the query scopes to the given criteria.
* This method merges {@link dbCriteria} with the given criteria parameter.
* It then resets {@link dbCriteria} to be null.
* @param EMongoCriteria|array $criteria the query criteria. This parameter may be modified by merging {@link dbCriteria}.
* @since v1.2.2
*/
public function applyScopes(&$criteria)
{
if($criteria === null)
{
$criteria = new EMongoCriteria();
}
else if(is_array($criteria))
{
$criteria = new EMongoCriteria($criteria);
}
else if(!($criteria instanceof EMongoCriteria))
throw new EMongoException('Cannot apply scopes to criteria');
if(($c=$this->getDbCriteria(false))!==null)
{
$c->mergeWith($criteria);
$criteria=$c;
$this->_criteria=null;
}
}
/**
* Saves the current record.
*
* The record is inserted as a row into the database table if its {@link isNewRecord}
* property is true (usually the case when the record is created using the 'new'
* operator). Otherwise, it will be used to update the corresponding row in the table
* (usually the case if the record is obtained using one of those 'find' methods.)
*
* Validation will be performed before saving the record. If the validation fails,
* the record will not be saved. You can call {@link getErrors()} to retrieve the
* validation errors.
*
* If the record is saved via insertion, its {@link isNewRecord} property will be
* set false, and its {@link scenario} property will be set to be 'update'.
* And if its primary key is auto-incremental and is not set before insertion,
* the primary key will be populated with the automatically generated key value.
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be saved to database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the saving succeeds
* @since v1.0
*/
public function save($runValidation=true,$attributes=null)
{
if(!$runValidation || $this->validate($attributes))
return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
else
return false;
}
/**
* Inserts a row into the table based on this active record attributes.
* If the table's primary key is auto-incremental and is null before insertion,
* it will be populated with the actual value after insertion.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
* and its {@link scenario} property will be set to be 'update'.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws CException if the record is not new
* @throws EMongoException on fail of insert or insert of empty document
* @throws MongoCursorException on fail of insert, when safe flag is set to true
* @throws MongoCursorTimeoutException on timeout of db operation , when safe flag is set to true
* @since v1.0
*/
public function insert(array $attributes=null)
{
if(!$this->getIsNewRecord())
throw new CDbException(Yii::t('yii','The EMongoDocument cannot be inserted to database because it is not new.'));
if($this->beforeSave())
{
Yii::trace(get_class($this).'.insert()','ext.MongoDb.EMongoDocument');
$rawData = $this->toArray();
// free the '_id' container if empty, mongo will not populate it if exists
if(empty($rawData['_id']))
unset($rawData['_id']);
// filter attributes if set in param
if($attributes!==null)
{
foreach($rawData as $key=>$value)
{
if(!in_array($key, $attributes))
unset($rawData[$key]);
}
}
if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
$result = $this->getCollection()->insert($rawData, array(
'fsync' => $this->getFsyncFlag(),
'safe' => $this->getSafeFlag()
));
else
$result = $this->getCollection()->insert($rawData, CPropertyValue::ensureBoolean($this->getSafeFlag()));
if($result !== false) // strict comparison needed
{
$this->_id = $rawData['_id'];
$this->setIsNewRecord(false);
$this->setScenario('update');
$this->afterSave();
return true;
}
throw new EMongoException(Yii::t('yii', 'Can\t save document to disk, or try to save empty document!'));
}
return false;
}
/**
* Updates the row represented by this active record.
* All loaded attributes will be saved to the database.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @param boolean modify if set true only selected attributes will be replaced, and not
* the whole document
* @return boolean whether the update is successful
* @throws CException if the record is new
* @throws EMongoException on fail of update
* @throws MongoCursorException on fail of update, when safe flag is set to true
* @throws MongoCursorTimeoutException on timeout of db operation , when safe flag is set to true
* @since v1.0
*/
public function update(array $attributes=null, $modify = false)
{
if($this->getIsNewRecord())
throw new CDbException(Yii::t('yii','The EMongoDocument cannot be updated because it is new.'));
if($this->beforeSave())
{
Yii::trace(get_class($this).'.update()','ext.MongoDb.EMongoDocument');
$rawData = $this->toArray();
// filter attributes if set in param
if($attributes!==null)
{
if (!in_array('_id', $attributes) && !$modify) $attributes[] = '_id'; // This is very easy to forget
foreach($rawData as $key=>$value)
{
if(!in_array($key, $attributes))
unset($rawData[$key]);
}
}
if($modify)
{
if(isset($rawData['_id']) === true)
unset($rawData['_id']);
$result = $this->getCollection()->update(
array('_id' => $this->_id),
array('$set' => $rawData),
array(
'fsync'=>$this->getFsyncFlag(),
'safe'=>$this->getSafeFlag(),
'multiple'=>false
)
);
} else {
if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
$result = $this->getCollection()->save($rawData, array(
'fsync'=>$this->getFsyncFlag(),
'safe'=>$this->getSafeFlag()
));
else
$result = $this->getCollection()->save($rawData);
}
if($result !== false) // strict comparison needed
{
$this->afterSave();
return true;
}
throw new CException(Yii::t('yii', 'Can\t save document to disk, or try to save empty document!'));
}
}
/**
* Atomic, in-place update method.
*
* @since v1.3.6
* @param EMongoModifier $modifier updating rules to apply
* @param EMongoCriteria $criteria condition to limit updating rules
* @return bool
*/
public function updateAll($modifier, $criteria=null) {
Yii::trace(get_class($this).'.updateAll()','ext.MongoDb.EMongoDocument');
if($modifier->canApply === true)
{
$this->applyScopes($criteria);
if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
$result = $this->getCollection()->update($criteria->getConditions(), $modifier->getModifiers(), array(
'fsync'=>$this->getFsyncFlag(),
'safe'=>$this->getSafeFlag(),
'upsert'=>false,
'multiple'=>true
));
else
$result = $this->getCollection()->update($criteria->getConditions(), $modifier->getModifiers(), array(
'upsert'=>false,
'multiple'=>true
));
return $result;
} else {
return false;
}
}
/**
* Deletes the row corresponding to this EMongoDocument.
* @return boolean whether the deletion is successful.
* @throws CException if the record is new
* @since v1.0
*/
public function delete()
{
if(!$this->getIsNewRecord())
{
Yii::trace(get_class($this).'.delete()','ext.MongoDb.EMongoDocument');
if($this->beforeDelete())
{
$result = $this->deleteByPk($this->getPrimaryKey());
if($result !== false)
{
$this->afterDelete();
$this->setIsNewRecord(true);
return true;
}
else
return false;
}
else
return false;
}
else
throw new CDbException(Yii::t('yii','The EMongoDocument cannot be deleted because it is new.'));
}
/**
* Deletes document with the specified primary key.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @since v1.0
*/
public function deleteByPk($pk, $criteria=null)
{
Yii::trace(get_class($this).'.deleteByPk()','ext.MongoDb.EMongoDocument');
if($this->beforeDelete())
{
$this->applyScopes($criteria);
$criteria->mergeWith($this->createPkCriteria($pk));
if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
$result = $this->getCollection()->remove($criteria->getConditions(), array(
'justOne'=>true,
'fsync'=>$this->getFsyncFlag(),
'safe'=>$this->getSafeFlag()
));
else
$result = $this->getCollection()->remove($criteria->getConditions(), true);
$this->afterDelete();
return $result;
}
return false;
}
/**
* Repopulates this active record with the latest data.
* @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
* @since v1.0
*/
public function refresh()
{
Yii::trace(get_class($this).'.refresh()','ext.MongoDb.EMongoDocument');
if(!$this->getIsNewRecord() && $this->getCollection()->count(array('_id'=>$this->_id))==1)
{
$this->setAttributes($this->getCollection()->find(array('_id'=>$this->_id)), false);
return true;
}
else
return false;
}
/**
* Finds a single EMongoDocument with the specified condition.
* @param array|EMongoCriteria $condition query criteria.
*
* If an array, it is treated as the initial values for constructing a {@link EMongoCriteria} object;
* Otherwise, it should be an instance of {@link EMongoCriteria}.
*
* @return EMongoDocument the record found. Null if no record is found.
* @since v1.0
*/
public function find($criteria=null)
{
Yii::trace(get_class($this).'.find()','ext.MongoDb.EMongoDocument');
if($this->beforeFind())
{
$this->applyScopes($criteria);
$doc = $this->getCollection()->findOne($criteria->getConditions(), $criteria->getSelect());
return $this->populateRecord($doc);
}
return null;
}
/**
* Finds all documents satisfying the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array|EMongoCriteria $condition query criteria.
* @return array list of documents satisfying the specified condition. An empty array is returned if none is found.
* @since v1.0
*/
public function findAll($criteria=null)
{
Yii::trace(get_class($this).'.findAll()','ext.MongoDb.EMongoDocument');
if($this->beforeFind())
{
$this->applyScopes($criteria);
$cursor = $this->getCollection()->find($criteria->getConditions());
if($criteria->getSort() !== null)
$cursor->sort($criteria->getSort());
if($criteria->getLimit() !== null)
$cursor->limit($criteria->getLimit());
if($criteria->getOffset() !== null)
$cursor->skip($criteria->getOffset());
if($criteria->getSelect())
$cursor->fields($criteria->getSelect(true));
if($this->getUseCursor())
return new EMongoCursor($cursor, $this->model());
else
return $this->populateRecords($cursor);
}
return array();
}
/**
* Finds document with the specified primary key.
* In MongoDB world every document has '_id' unique field, so with this method that
* field is in use as PK!
* See {@link find()} for detailed explanation about $condition.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @return the document found. An null is returned if none is found.
* @since v1.0
*/
public function findByPk($pk, $criteria=null)
{
Yii::trace(get_class($this).'.findByPk()','ext.MongoDb.EMongoDocument');
$criteria = new EMongoCriteria($criteria);
$criteria->mergeWith($this->createPkCriteria($pk));
return $this->find($criteria);
}
/**
* Finds all documents with the specified primary keys.
* In MongoDB world every document has '_id' unique field, so with this method that
* field is in use as PK by default.
* See {@link find()} for detailed explanation about $condition.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @return the document found. An null is returned if none is found.
* @since v1.0
*/
public function findAllByPk($pk, $criteria=null)
{
Yii::trace(get_class($this).'.findAllByPk()','ext.MongoDb.EMongoDocument');
$criteria = new EMongoCriteria($criteria);
$criteria->mergeWith($this->createPkCriteria($pk, true));
return $this->findAll($criteria);
}
/**
* Finds document with the specified attributes.
*
* See {@link find()} for detailed explanation about $condition.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @return the document found. An null is returned if none is found.
* @since v1.0
*/
public function findByAttributes(array $attributes)
{
$criteria = new EMongoCriteria();
foreach($attributes as $name=>$value)
{
$criteria->$name('==', $value);
}
return $this->find($criteria);
}
/**
* Finds all documents with the specified attributes.
*
* See {@link find()} for detailed explanation about $condition.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @return the document found. An null is returned if none is found.
* @since v1.0
*/
public function findAllByAttributes(array $attributes)
{
$criteria = new EMongoCriteria();
foreach($attributes as $name=>$value)
{
$criteria->$name('==', $value);
}
return $this->findAll($criteria);
}
/**
* Counts all documents satisfying the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array|EMongoCriteria $condition query criteria.
* @return integer Count of all documents satisfying the specified condition.
* @since v1.0
*/
public function count($criteria=null)
{
Yii::trace(get_class($this).'.count()','ext.MongoDb.EMongoDocument');
$this->applyScopes($criteria);
return $this->getCollection()->count($criteria->getConditions());
}
/**
* Counts all documents satisfying the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array|EMongoCriteria $condition query criteria.
* @return integer Count of all documents satisfying the specified condition.
* @since v1.2.2
*/
public function countByAttributes(array $attributes)
{
Yii::trace(get_class($this).'.countByAttributes()','ext.MongoDb.EMongoDocument');
$criteria = new EMongoCriteria;
foreach($attributes as $name=>$value)
$criteria->$name = $value;
$this->applyScopes($criteria);
return $this->getCollection()->count($criteria->getConditions());
}
/**
* Deletes documents with the specified primary keys.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array|EMongoCriteria $condition query criteria.
* @since v1.0
*/
public function deleteAll($criteria=null)
{
Yii::trace(get_class($this).'.deleteByPk()','ext.MongoDb.EMongoDocument');
$this->applyScopes($criteria);
if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
return $this->getCollection()->remove($criteria->getConditions(), array(
'justOne'=>false,
'fsync'=>$this->getFsyncFlag(),
'safe'=>$this->getSafeFlag()
));
else
return $this->getCollection()->remove($criteria->getConditions(), false);
}
/**
* This event is raised before the record is saved.
* By setting {@link CModelEvent::isValid} to be false, the normal {@link save()} process will be stopped.
* @param CModelEvent $event the event parameter
* @since v1.0
*/
public function onBeforeSave($event)
{
$this->raiseEvent('onBeforeSave',$event);
}
/**
* This event is raised after the record is saved.
* @param CEvent $event the event parameter
* @since v1.0
*/
public function onAfterSave($event)
{
$this->raiseEvent('onAfterSave',$event);
}
/**
* This event is raised before the record is deleted.
* By setting {@link CModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped.
* @param CModelEvent $event the event parameter
* @since v1.0
*/
public function onBeforeDelete($event)
{
$this->raiseEvent('onBeforeDelete',$event);
}
/**
* This event is raised after the record is deleted.
* @param CEvent $event the event parameter
* @since v1.0
*/
public function onAfterDelete($event)
{
$this->raiseEvent('onAfterDelete',$event);
}
/**
* This event is raised before finder performs a find call.
* In this event, the {@link CModelEvent::criteria} property contains the query criteria
* passed as parameters to those find methods. If you want to access
* the query criteria specified in scopes, please use {@link getDbCriteria()}.
* You can modify either criteria to customize them based on needs.
* @param CModelEvent $event the event parameter
* @see beforeFind
* @since v1.0
*/
public function onBeforeFind($event)
{
$this->raiseEvent('onBeforeFind',$event);
}
/**
* This event is raised after the record is instantiated by a find method.
* @param CEvent $event the event parameter
* @since v1.0
*/
public function onAfterFind($event)
{
$this->raiseEvent('onAfterFind',$event);
}
/**
* This method is invoked before saving a record (after validation, if any).
* The default implementation raises the {@link onBeforeSave} event.
* You may override this method to do any preparation work for record saving.
* Use {@link isNewRecord} to determine whether the saving is
* for inserting or updating record.
* Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the saving should be executed. Defaults to true.
* @since v1.0
*/
protected function beforeSave()
{
if($this->hasEventHandler('onBeforeSave'))
{
$event=new CModelEvent($this);
$this->onBeforeSave($event);
return $event->isValid;
}
else
return true;
}
/**
* This method is invoked after saving a record successfully.
* The default implementation raises the {@link onAfterSave} event.
* You may override this method to do postprocessing after record saving.
* Make sure you call the parent implementation so that the event is raised properly.
* @since v1.0
*/
protected function afterSave()
{
if($this->hasEventHandler('onAfterSave'))
$this->onAfterSave(new CEvent($this));
}
/**
* This method is invoked before deleting a record.
* The default implementation raises the {@link onBeforeDelete} event.
* You may override this method to do any preparation work for record deletion.
* Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the record should be deleted. Defaults to true.
* @since v1.0
*/
protected function beforeDelete()
{
if($this->hasEventHandler('onBeforeDelete'))
{
$event=new CModelEvent($this);
$this->onBeforeDelete($event);
return $event->isValid;
}
else
return true;
}
/**
* This method is invoked after deleting a record.
* The default implementation raises the {@link onAfterDelete} event.
* You may override this method to do postprocessing after the record is deleted.
* Make sure you call the parent implementation so that the event is raised properly.
* @since v1.0
*/
protected function afterDelete()
{
if($this->hasEventHandler('onAfterDelete'))
$this->onAfterDelete(new CEvent($this));
}
/**
* This method is invoked before an AR finder executes a find call.
* The find calls include {@link find}, {@link findAll}, {@link findByPk},
* {@link findAllByPk}, {@link findByAttributes} and {@link findAllByAttributes}.
* The default implementation raises the {@link onBeforeFind} event.
* If you override this method, make sure you call the parent implementation
* so that the event is raised properly.
*
* Starting from version 1.1.5, this method may be called with a hidden {@link CDbCriteria}
* parameter which represents the current query criteria as passed to a find method of AR.
* @since v1.0
*/
protected function beforeFind()
{
if($this->hasEventHandler('onBeforeFind'))
{
$event=new CModelEvent($this);
$this->onBeforeFind($event);
return $event->isValid;
}
else
return true;
}
/**
* This method is invoked after each record is instantiated by a find method.
* The default implementation raises the {@link onAfterFind} event.
* You may override this method to do postprocessing after each newly found record is instantiated.
* Make sure you call the parent implementation so that the event is raised properly.
* @since v1.0
*/
protected function afterFind()
{
if($this->hasEventHandler('onAfterFind'))
$this->onAfterFind(new CEvent($this));
}
/**
* Creates an document instance.
* This method is called by {@link populateRecord} and {@link populateRecords}.
* You may override this method if the instance being created
* depends the attributes that are to be populated to the record.
* @param array $attributes list of attribute values for the active records.
* @return EMongoDocument the document
* @since v1.0
*/
protected function instantiate($attributes)
{
$class=get_class($this);
$model=new $class(null);
$model->initEmbeddedDocuments();
$model->setAttributes($attributes, false);
return $model;
}
/**
* Creates an EMongoDocument with the given attributes.
* This method is internally used by the find methods.
* @param array $attributes attribute values (column name=>column value)
* @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated.
* This parameter is added in version 1.0.3.
* @return EMongoDocument the newly created document. The class of the object is the same as the model class.
* Null is returned if the input data is false.
* @since v1.0
*/
public function populateRecord($document, $callAfterFind=true)
{
if($document!==null)
{
$model=$this->instantiate($document);
$model->setScenario('update');
$model->init();
$model->attachBehaviors($model->behaviors());
if($callAfterFind)
$model->afterFind();
return $model;
}
else
return null;
}
/**
* Creates a list of documents based on the input data.
* This method is internally used by the find methods.
* @param array $data list of attribute values for the active records.
* @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated.
* This parameter is added in version 1.0.3.
* @param string $index the name of the attribute whose value will be used as indexes of the query result array.
* If null, it means the array will be indexed by zero-based integers.
* @return array list of active records.
* @since v1.0
*/
public function populateRecords($data, $callAfterFind=true, $index=null)
{
$records=array();
foreach($data as $attributes)
{
if(($record=$this->populateRecord($attributes,$callAfterFind))!==null)
{
if($index===null)
$records[]=$record;
else
$records[$record->$index]=$record;
}
}
return $records;
}
/**
* Magic search method, provides basic search functionality.
*
* Returns EMongoDocument object ($this) with criteria set to
* rexexp: /$attributeValue/i
* used for Data provider search functionality
* @param boolean $caseSensitive whathever do a case-sensitive search, default to false
* @return EMongoDocument
* @since v1.2.2
*/
public function search($caseSensitive = false)
{
$criteria = $this->getDbCriteria();
foreach($this->getSafeAttributeNames() as $attribute)
{
if($this->$attribute !== null && $this->$attribute !== '')
{
if(is_array($this->$attribute) || is_object($this->$attribute))
$criteria->$attribute = $this->$attribute;
else if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=|!=|==))?(.*)$/',$this->$attribute,$matches))
{
$op = $matches[1];
$value = $matches[2];
if($op === '=') $op = '==';
if($op !== '')
call_user_func(array($criteria, $attribute), $op, is_numeric($value) ? floatval($value) : $value);
else
$criteria->$attribute = new MongoRegex($caseSensitive ? '/'.$this->$attribute.'/' : '/'.$this->$attribute.'/i');
}
}
}
$this->setDbCriteria($criteria);
return new EMongoDocumentDataProvider($this);
}
/**
* Returns the static model of the specified EMongoDocument class.
* The model returned is a static instance of the EMongoDocument class.
* It is provided for invoking class-level methods (something similar to static class methods.)
*
* EVERY derived EMongoDocument class must override this method as follows,
* <pre>
* public static function model($className=__CLASS__)
* {
* return parent::model($className);
* }
* </pre>
*
* @param string $className EMongoDocument class name.
* @return EMongoDocument EMongoDocument model instance.
* @since v1.0
*/
public static function model($className=__CLASS__)
{
if(isset(self::$_models[$className]))
return self::$_models[$className];
else
{
$model=self::$_models[$className]=new $className(null);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
/**
* @since v1.2.2
*/
private function createPkCriteria($pk, $multiple=false)
{
$pkField = $this->primaryKey();
$criteria = new EMongoCriteria();
if(is_string($pkField))
{
if(!$multiple)
$criteria->{$pkField} = $pk;
else
$criteria->{$pkField}('in', $pk);
}
else if(is_array($pkField))
{
if(!$multiple)
for($i=0; $i<count($pkField); $i++)
$criteria->{$pkField[$i]} = $pk[$i];
else
throw new EMongoException(Yii::t('yii', 'Cannot create PK criteria for multiple composite key\'s (not implemented yet)'));
}
return $criteria;
}
}
Something went wrong with that request. Please try again.