123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368 |
- <?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
- protected $auto_fields = array(); // 由模型来自动补全的字段
- protected $optional_fields = array(); // 可选字段,如果为null,save时自动过滤掉,已废弃
- protected $unneed_update_fields = array();// 不用更新的字段,一些字段只用在后台展示,不在后台进行修改,避免产生问题~
- /**
- * 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()
- {
- $db_name = $this->getMongoDBComponent()->dbName;
- if(!isset(self::$_collections[$db_name . '_' . $this->getCollectionName()]))
- self::$_collections[$db_name . '_' . $this->getCollectionName()] = $this->getDb()->selectCollection($this->getCollectionName());
- return self::$_collections[$db_name . '_' . $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:
- * - Criteria level
- * - Object level
- * - Model level
- * - Glopal level (always set)
- * @return boolean
- */
- public function getUseCursor($criteria = null)
- {
- if($criteria !== null && $criteria->getUseCursor() !== null)
- return $criteria->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,$modify=false)
- {
- if(!$runValidation || $this->validate($attributes))
- return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes,$modify);
- 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) && !in_array($key, $this->auto_fields)){
- unset($rawData[$key]);
- }
- }
- } else {
- foreach($rawData as $key=>$value){
- if($value === null){
- unset($rawData[$key]);
- }
- }
- }
-
- if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
- $result = $this->getCollection()->insert($rawData, array(
- 'fsync' => $this->getFsyncFlag(),
- 'w' => $this->getSafeFlag()
- ));
- else
- $result = $this->getCollection()->insert($rawData, CPropertyValue::ensureBoolean($this->getSafeFlag()));
- if($result !== false) // strict comparison needed
- {
- $this->_id = $rawData['_id'];
- $this->afterSave();
- $this->setIsNewRecord(false);
- $this->setScenario('update');
- 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();
- //调整更新策略,不在全部更新,避免覆盖掉其它系统用到的字段
- $unset_arr = array();
- if ($attributes !== null){
- foreach($rawData as $key=>$value)
- {
- if(!in_array($key, $attributes) && !in_array($key, $this->auto_fields)){
- unset($rawData[$key]);
- }
- }
- } else {
- foreach($rawData as $key=>$value){
- if (in_array($key, $this->unneed_update_fields)){
- unset($rawData[$key]);
- continue;
- }
- if ($value === null){
- unset($rawData[$key]);
- $unset_arr[$key] = 1;
- }
- }
- }
- 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(),
- 'w'=>$this->getSafeFlag(),
- 'multiple'=>false
- )
- );
- } else {
- if (isset($rawData['_id']) === true){
- unset($rawData['_id']);
- }
- $update = array('$set' => $rawData);
- if (!empty($unset_arr)){
- $update['$unset'] = $unset_arr;
- }
- $result = $this->getCollection()->update(
- array('_id' => $this->_id),
- $update,
- array(
- 'fsync'=>$this->getFsyncFlag(),
- 'w'=>$this->getSafeFlag(),
- 'multiple'=>false
- )
- );
-
- }
- 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');
- $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(),
- 'w'=>$this->getSafeFlag()
- ));
- else
- $result = $this->getCollection()->remove($criteria->getConditions(), true);
- return $result;
- }
- /**
- * 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($criteria))
- 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;
- }
- }
|