EMongoDocument.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368
  1. <?php
  2. /**
  3. * EMongoDocument.php
  4. *
  5. * PHP version 5.2+
  6. *
  7. * @author Dariusz Górecki <darek.krk@gmail.com>
  8. * @author Invenzzia Group, open-source division of CleverIT company http://www.invenzzia.org
  9. * @copyright 2011 CleverIT http://www.cleverit.com.pl
  10. * @license http://www.yiiframework.com/license/ BSD license
  11. * @version 1.3
  12. * @category ext
  13. * @package ext.YiiMongoDbSuite
  14. * @since v1.0
  15. */
  16. /**
  17. * EMongoDocument
  18. *
  19. * @property-read MongoDB $db
  20. * @since v1.0
  21. */
  22. abstract class EMongoDocument extends EMongoEmbeddedDocument
  23. {
  24. private $_new = false; // whether this instance is new or not
  25. private $_criteria = null; // query criteria (used by finder only)
  26. /**
  27. * Static array that holds mongo collection object instances,
  28. * protected access since v1.3
  29. *
  30. * @var array $_collections static array of loaded collection objects
  31. * @since v1.3
  32. */
  33. protected static $_collections = array(); // MongoCollection object
  34. private static $_models = array();
  35. private static $_indexes = array(); // Hold collection indexes array
  36. private $_fsyncFlag = null; // Object level FSync flag
  37. private $_safeFlag = null; // Object level Safe flag
  38. protected $useCursor = null; // Whatever to return cursor instead on raw array
  39. /**
  40. * @var boolean $ensureIndexes whatever to check and create non existing indexes of collection
  41. * @since v1.1
  42. */
  43. protected $ensureIndexes = true; // Whatever to ensure indexes
  44. protected $auto_fields = array(); // 由模型来自动补全的字段
  45. protected $optional_fields = array(); // 可选字段,如果为null,save时自动过滤掉,已废弃
  46. protected $unneed_update_fields = array();// 不用更新的字段,一些字段只用在后台展示,不在后台进行修改,避免产生问题~
  47. /**
  48. * EMongoDB component static instance
  49. * @var EMongoDB $_emongoDb;
  50. * @since v1.0
  51. */
  52. protected static $_emongoDb;
  53. /**
  54. * MongoDB special field, every document has to have this
  55. *
  56. * @var mixed $_id
  57. * @since v1.0
  58. */
  59. public $_id;
  60. /**
  61. * Add scopes functionality
  62. * @see CComponent::__call()
  63. * @since v1.0
  64. */
  65. public function __call($name, $parameters)
  66. {
  67. $scopes=$this->scopes();
  68. if(isset($scopes[$name]))
  69. {
  70. $this->getDbCriteria()->mergeWith($scopes[$name]);
  71. return $this;
  72. }
  73. return parent::__call($name,$parameters);
  74. }
  75. /**
  76. * Constructor {@see setScenario()}
  77. *
  78. * @param string $scenario
  79. * @since v1.0
  80. */
  81. public function __construct($scenario='insert')
  82. {
  83. if($scenario==null) // internally used by populateRecord() and model()
  84. return;
  85. $this->setScenario($scenario);
  86. $this->setIsNewRecord(true);
  87. $this->init();
  88. $this->attachBehaviors($this->behaviors());
  89. $this->afterConstruct();
  90. $this->initEmbeddedDocuments();
  91. }
  92. /**
  93. * Return the primary key field for this collection, defaults to '_id'
  94. * @return string|array field name, or array of fields for composite primary key
  95. * @since v1.2.2
  96. */
  97. public function primaryKey()
  98. {
  99. return '_id';
  100. }
  101. /**
  102. * @since v1.2.2
  103. */
  104. public function getPrimaryKey()
  105. {
  106. $pk = $this->primaryKey();
  107. if(is_string($pk))
  108. return $this->{$pk};
  109. else
  110. {
  111. $return = array();
  112. foreach($pk as $pkFiled)
  113. $return[] = $this->{$pkFiled};
  114. return $return;
  115. }
  116. }
  117. /**
  118. * Get EMongoDB component instance
  119. * By default it is mongodb application component
  120. *
  121. * @return EMongoDB
  122. * @since v1.0
  123. */
  124. public function getMongoDBComponent()
  125. {
  126. if(self::$_emongoDb===null)
  127. self::$_emongoDb = Yii::app()->getComponent('mongodb');
  128. return self::$_emongoDb;
  129. }
  130. /**
  131. * Set EMongoDB component instance
  132. * @param EMongoDB $component
  133. * @since v1.0
  134. */
  135. public function setMongoDBComponent(EMongoDB $component)
  136. {
  137. self::$_emongoDb = $component;
  138. }
  139. /**
  140. * Get raw MongoDB instance
  141. * @return MongoDB
  142. * @since v1.0
  143. */
  144. public function getDb()
  145. {
  146. return $this->getMongoDBComponent()->getDbInstance();
  147. }
  148. /**
  149. * This method must return collection name for use with this model
  150. * this must be implemented in child classes
  151. *
  152. * this is read-only defined only at class define
  153. * if you whant to set different colection during run-time
  154. * use {@see setCollection()}
  155. *
  156. * @return string collection name
  157. * @since v1.0
  158. */
  159. abstract public function getCollectionName();
  160. /**
  161. * Returns current MongoCollection object
  162. * By default this method use {@see getCollectionName()}
  163. * @return MongoCollection
  164. * @since v1.0
  165. */
  166. public function getCollection()
  167. {
  168. $db_name = $this->getMongoDBComponent()->dbName;
  169. if(!isset(self::$_collections[$db_name . '_' . $this->getCollectionName()]))
  170. self::$_collections[$db_name . '_' . $this->getCollectionName()] = $this->getDb()->selectCollection($this->getCollectionName());
  171. return self::$_collections[$db_name . '_' . $this->getCollectionName()];
  172. }
  173. /**
  174. * Set current MongoCollection object
  175. * @param MongoCollection $collection
  176. * @since v1.0
  177. */
  178. public function setCollection(MongoCollection $collection)
  179. {
  180. self::$_collections[$this->getCollectionName()] = $collection;
  181. }
  182. /**
  183. * Returns if the current record is new.
  184. * @return boolean whether the record is new and should be inserted when calling {@link save}.
  185. * This property is automatically set in constructor and {@link populateRecord}.
  186. * Defaults to false, but it will be set to true if the instance is created using
  187. * the new operator.
  188. * @since v1.0
  189. */
  190. public function getIsNewRecord()
  191. {
  192. return $this->_new;
  193. }
  194. /**
  195. * Sets if the record is new.
  196. * @param boolean $value whether the record is new and should be inserted when calling {@link save}.
  197. * @see getIsNewRecord
  198. * @since v1.0
  199. */
  200. public function setIsNewRecord($value)
  201. {
  202. $this->_new=$value;
  203. }
  204. /**
  205. * Returns the mongo criteria associated with this model.
  206. * @param boolean $createIfNull whether to create a criteria instance if it does not exist. Defaults to true.
  207. * @return EMongoCriteria the query criteria that is associated with this model.
  208. * This criteria is mainly used by {@link scopes named scope} feature to accumulate
  209. * different criteria specifications.
  210. * @since v1.0
  211. */
  212. public function getDbCriteria($createIfNull=true)
  213. {
  214. if($this->_criteria===null)
  215. if(($c = $this->defaultScope()) !== array() || $createIfNull)
  216. $this->_criteria = new EMongoCriteria($c);
  217. return $this->_criteria;
  218. }
  219. /**
  220. * Set girrent object, this will override proevious criteria
  221. *
  222. * @param EMongoCriteria $criteria
  223. * @since v1.0
  224. */
  225. public function setDbCriteria($criteria)
  226. {
  227. if(is_array($criteria))
  228. $this->_criteria = new EMongoCriteria($criteria);
  229. else if($criteria instanceof EMongoCriteria)
  230. $this->_criteria = $criteria;
  231. else
  232. $this->_criteria = new EMongoCriteria();
  233. }
  234. /**
  235. * Get FSync flag
  236. *
  237. * It will return the nearest not null value in order:
  238. * - Object level
  239. * - Model level
  240. * - Glopal level (always set)
  241. * @return boolean
  242. */
  243. public function getFsyncFlag()
  244. {
  245. if($this->_fsyncFlag !== null)
  246. return $this->_fsyncFlag; // We have flag set, return it
  247. if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->_fsyncFlag !== null))
  248. return self::$_models[get_class($this)]->_fsyncFlag; // Model have flag set, return it
  249. return $this->getMongoDBComponent()->fsyncFlag;
  250. }
  251. /**
  252. * Set object level FSync flag
  253. * @param boolean $flag true|false value for FSync flag
  254. */
  255. public function setFsyncFlag($flag)
  256. {
  257. $this->_fsyncFlag = ($flag == true);
  258. if($this->_fsyncFlag)
  259. $this->setSafeFlag(true); // Setting FSync flag to true will implicit set safe to true
  260. }
  261. /**
  262. * Get Safe flag
  263. *
  264. * It will return the nearest not null value in order:
  265. * - Object level
  266. * - Model level
  267. * - Glopal level (always set)
  268. * @return boolean
  269. */
  270. public function getSafeFlag()
  271. {
  272. if($this->_safeFlag !== null)
  273. return $this->_safeFlag; // We have flag set, return it
  274. if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->_safeFlag !== null))
  275. return self::$_models[get_class($this)]->_safeFlag; // Model have flag set, return it
  276. return $this->getMongoDBComponent()->safeFlag;
  277. }
  278. /**
  279. * Set object level Safe flag
  280. * @param boolean $flag true|false value for Safe flag
  281. */
  282. public function setSafeFlag($flag)
  283. {
  284. $this->_safeFlag = ($flag == true);
  285. }
  286. /**
  287. * Get value of use cursor flag
  288. *
  289. * It will return the nearest not null value in order:
  290. * - Criteria level
  291. * - Object level
  292. * - Model level
  293. * - Glopal level (always set)
  294. * @return boolean
  295. */
  296. public function getUseCursor($criteria = null)
  297. {
  298. if($criteria !== null && $criteria->getUseCursor() !== null)
  299. return $criteria->getUseCursor();
  300. if($this->useCursor !== null)
  301. return $this->useCursor; // We have flag set, return it
  302. if((isset(self::$_models[get_class($this)]) === true) && (self::$_models[get_class($this)]->useCursor !== null))
  303. return self::$_models[get_class($this)]->useCursor; // Model have flag set, return it
  304. return $this->getMongoDBComponent()->useCursor;
  305. }
  306. /**
  307. * Set object level value of use cursor flag
  308. * @param boolean $useCursor true|false value for use cursor flag
  309. */
  310. public function setUseCursor($useCursor)
  311. {
  312. $this->useCursor = ($useCursor == true);
  313. }
  314. /**
  315. * Sets the attribute values in a massive way.
  316. * @param array $values attribute values (name=>value) to be set.
  317. * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
  318. * A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
  319. * @see getSafeAttributeNames
  320. * @see attributeNames
  321. * @since v1.3.1
  322. */
  323. public function setAttributes($values, $safeOnly=true)
  324. {
  325. if(!is_array($values))
  326. return;
  327. if($this->hasEmbeddedDocuments())
  328. {
  329. $attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
  330. foreach($this->embeddedDocuments() as $fieldName => $className)
  331. if(isset($values[$fieldName]) && isset($attributes[$fieldName]))
  332. {
  333. $this->$fieldName->setAttributes($values[$fieldName], $safeOnly);
  334. unset($values[$fieldName]);
  335. }
  336. }
  337. parent::setAttributes($values, $safeOnly);
  338. }
  339. /**
  340. * This function check indexes and applies them to the collection if needed
  341. * see CModel::init()
  342. *
  343. * @see EMongoEmbeddedDocument::init()
  344. * @since v1.1
  345. */
  346. public function init()
  347. {
  348. parent::init();
  349. if($this->ensureIndexes && !isset(self::$_indexes[$this->getCollectionName()]))
  350. {
  351. $indexInfo = $this->getCollection()->getIndexInfo();
  352. array_shift($indexInfo); // strip out default _id index
  353. $indexes = array();
  354. foreach($indexInfo as $index)
  355. {
  356. $indexes[$index['name']] = array(
  357. 'key'=>$index['key'],
  358. 'unique'=>isset($index['unique']) ? $index['unique'] : false,
  359. );
  360. }
  361. self::$_indexes[$this->getCollectionName()] = $indexes;
  362. $this->ensureIndexes();
  363. }
  364. }
  365. /**
  366. * This function may return array of indexes for this collection
  367. * array sytntax is:
  368. * return array(
  369. * 'index_name'=>array('key'=>array('fieldName1'=>EMongoCriteria::SORT_ASC, 'fieldName2'=>EMongoCriteria::SORT_DESC),
  370. * 'index2_name'=>array('key'=>array('fieldName3'=>EMongoCriteria::SORT_ASC, 'unique'=>true),
  371. * );
  372. * @return array list of indexes for this collection
  373. * @since v1.1
  374. */
  375. public function indexes()
  376. {
  377. return array();
  378. }
  379. /**
  380. * @since v1.1
  381. */
  382. private function ensureIndexes()
  383. {
  384. $indexNames = array_keys(self::$_indexes[$this->getCollectionName()]);
  385. foreach($this->indexes() as $name=>$index)
  386. {
  387. if(!in_array($name, $indexNames))
  388. {
  389. if(version_compare(Mongo::VERSION, '1.0.2','>=') === true)
  390. {
  391. $this->getCollection()->ensureIndex(
  392. $index['key'],
  393. array('unique'=>isset($index['unique']) ? $index['unique'] : false, 'name'=>$name)
  394. );
  395. } else {
  396. $this->getCollection()->ensureIndex(
  397. $index['key'],
  398. isset($index['unique']) ? $index['unique'] : false
  399. );
  400. }
  401. self::$_indexes[$this->getCollectionName()][$name] = $index;
  402. }
  403. }
  404. }
  405. /**
  406. * Returns the declaration of named scopes.
  407. * A named scope represents a query criteria that can be chained together with
  408. * other named scopes and applied to a query. This method should be overridden
  409. * by child classes to declare named scopes for the particular document classes.
  410. * For example, the following code declares two named scopes: 'recently' and
  411. * 'published'.
  412. * <pre>
  413. * return array(
  414. * 'published'=>array(
  415. * 'conditions'=>array(
  416. * 'status'=>array('==', 1),
  417. * ),
  418. * ),
  419. * 'recently'=>array(
  420. * 'sort'=>array('create_time'=>EMongoCriteria::SORT_DESC),
  421. * 'limit'=>5,
  422. * ),
  423. * );
  424. * </pre>
  425. * If the above scopes are declared in a 'Post' model, we can perform the following
  426. * queries:
  427. * <pre>
  428. * $posts=Post::model()->published()->findAll();
  429. * $posts=Post::model()->published()->recently()->findAll();
  430. * $posts=Post::model()->published()->published()->recently()->find();
  431. * </pre>
  432. *
  433. * @return array the scope definition. The array keys are scope names; the array
  434. * values are the corresponding scope definitions. Each scope definition is represented
  435. * as an array whose keys must be properties of {@link EMongoCriteria}.
  436. * @since v1.0
  437. */
  438. public function scopes()
  439. {
  440. return array();
  441. }
  442. /**
  443. * Returns the default named scope that should be implicitly applied to all queries for this model.
  444. * Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.
  445. * The default implementation simply returns an empty array. You may override this method
  446. * if the model needs to be queried with some default criteria (e.g. only active records should be returned).
  447. * @return array the mongo criteria. This will be used as the parameter to the constructor
  448. * of {@link EMongoCriteria}.
  449. * @since v1.2.2
  450. */
  451. public function defaultScope()
  452. {
  453. return array();
  454. }
  455. /**
  456. * Resets all scopes and criterias applied including default scope.
  457. *
  458. * @return EMongoDocument
  459. * @since v1.0
  460. */
  461. public function resetScope()
  462. {
  463. $this->_criteria = new EMongoCriteria();
  464. return $this;
  465. }
  466. /**
  467. * Applies the query scopes to the given criteria.
  468. * This method merges {@link dbCriteria} with the given criteria parameter.
  469. * It then resets {@link dbCriteria} to be null.
  470. * @param EMongoCriteria|array $criteria the query criteria. This parameter may be modified by merging {@link dbCriteria}.
  471. * @since v1.2.2
  472. */
  473. public function applyScopes(&$criteria)
  474. {
  475. if($criteria === null)
  476. {
  477. $criteria = new EMongoCriteria();
  478. }
  479. else if(is_array($criteria))
  480. {
  481. $criteria = new EMongoCriteria($criteria);
  482. }
  483. else if(!($criteria instanceof EMongoCriteria))
  484. throw new EMongoException('Cannot apply scopes to criteria');
  485. if(($c=$this->getDbCriteria(false))!==null)
  486. {
  487. $c->mergeWith($criteria);
  488. $criteria=$c;
  489. $this->_criteria=null;
  490. }
  491. }
  492. /**
  493. * Saves the current record.
  494. *
  495. * The record is inserted as a row into the database table if its {@link isNewRecord}
  496. * property is true (usually the case when the record is created using the 'new'
  497. * operator). Otherwise, it will be used to update the corresponding row in the table
  498. * (usually the case if the record is obtained using one of those 'find' methods.)
  499. *
  500. * Validation will be performed before saving the record. If the validation fails,
  501. * the record will not be saved. You can call {@link getErrors()} to retrieve the
  502. * validation errors.
  503. *
  504. * If the record is saved via insertion, its {@link isNewRecord} property will be
  505. * set false, and its {@link scenario} property will be set to be 'update'.
  506. * And if its primary key is auto-incremental and is not set before insertion,
  507. * the primary key will be populated with the automatically generated key value.
  508. *
  509. * @param boolean $runValidation whether to perform validation before saving the record.
  510. * If the validation fails, the record will not be saved to database.
  511. * @param array $attributes list of attributes that need to be saved. Defaults to null,
  512. * meaning all attributes that are loaded from DB will be saved.
  513. * @return boolean whether the saving succeeds
  514. * @since v1.0
  515. */
  516. public function save($runValidation=true,$attributes=null,$modify=false)
  517. {
  518. if(!$runValidation || $this->validate($attributes))
  519. return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes,$modify);
  520. else
  521. return false;
  522. }
  523. /**
  524. * Inserts a row into the table based on this active record attributes.
  525. * If the table's primary key is auto-incremental and is null before insertion,
  526. * it will be populated with the actual value after insertion.
  527. * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
  528. * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
  529. * and its {@link scenario} property will be set to be 'update'.
  530. * @param array $attributes list of attributes that need to be saved. Defaults to null,
  531. * meaning all attributes that are loaded from DB will be saved.
  532. * @return boolean whether the attributes are valid and the record is inserted successfully.
  533. * @throws CException if the record is not new
  534. * @throws EMongoException on fail of insert or insert of empty document
  535. * @throws MongoCursorException on fail of insert, when safe flag is set to true
  536. * @throws MongoCursorTimeoutException on timeout of db operation , when safe flag is set to true
  537. * @since v1.0
  538. */
  539. public function insert(array $attributes=null)
  540. {
  541. if(!$this->getIsNewRecord())
  542. throw new CDbException(Yii::t('yii','The EMongoDocument cannot be inserted to database because it is not new.'));
  543. if($this->beforeSave())
  544. {
  545. Yii::trace(get_class($this).'.insert()','ext.MongoDb.EMongoDocument');
  546. $rawData = $this->toArray();
  547. // free the '_id' container if empty, mongo will not populate it if exists
  548. if(empty($rawData['_id']))
  549. unset($rawData['_id']);
  550. // filter attributes if set in param
  551. if($attributes!==null){
  552. foreach($rawData as $key=>$value)
  553. {
  554. if(!in_array($key, $attributes) && !in_array($key, $this->auto_fields)){
  555. unset($rawData[$key]);
  556. }
  557. }
  558. } else {
  559. foreach($rawData as $key=>$value){
  560. if($value === null){
  561. unset($rawData[$key]);
  562. }
  563. }
  564. }
  565. if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
  566. $result = $this->getCollection()->insert($rawData, array(
  567. 'fsync' => $this->getFsyncFlag(),
  568. 'w' => $this->getSafeFlag()
  569. ));
  570. else
  571. $result = $this->getCollection()->insert($rawData, CPropertyValue::ensureBoolean($this->getSafeFlag()));
  572. if($result !== false) // strict comparison needed
  573. {
  574. $this->_id = $rawData['_id'];
  575. $this->afterSave();
  576. $this->setIsNewRecord(false);
  577. $this->setScenario('update');
  578. return true;
  579. }
  580. throw new EMongoException(Yii::t('yii', 'Can\t save document to disk, or try to save empty document!'));
  581. }
  582. return false;
  583. }
  584. /**
  585. * Updates the row represented by this active record.
  586. * All loaded attributes will be saved to the database.
  587. * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
  588. * @param array $attributes list of attributes that need to be saved. Defaults to null,
  589. * meaning all attributes that are loaded from DB will be saved.
  590. * @param boolean modify if set true only selected attributes will be replaced, and not
  591. * the whole document
  592. * @return boolean whether the update is successful
  593. * @throws CException if the record is new
  594. * @throws EMongoException on fail of update
  595. * @throws MongoCursorException on fail of update, when safe flag is set to true
  596. * @throws MongoCursorTimeoutException on timeout of db operation , when safe flag is set to true
  597. * @since v1.0
  598. */
  599. public function update(array $attributes=null, $modify = false)
  600. {
  601. if($this->getIsNewRecord())
  602. throw new CDbException(Yii::t('yii','The EMongoDocument cannot be updated because it is new.'));
  603. if($this->beforeSave())
  604. {
  605. Yii::trace(get_class($this).'.update()','ext.MongoDb.EMongoDocument');
  606. $rawData = $this->toArray();
  607. //调整更新策略,不在全部更新,避免覆盖掉其它系统用到的字段
  608. $unset_arr = array();
  609. if ($attributes !== null){
  610. foreach($rawData as $key=>$value)
  611. {
  612. if(!in_array($key, $attributes) && !in_array($key, $this->auto_fields)){
  613. unset($rawData[$key]);
  614. }
  615. }
  616. } else {
  617. foreach($rawData as $key=>$value){
  618. if (in_array($key, $this->unneed_update_fields)){
  619. unset($rawData[$key]);
  620. continue;
  621. }
  622. if ($value === null){
  623. unset($rawData[$key]);
  624. $unset_arr[$key] = 1;
  625. }
  626. }
  627. }
  628. if ($modify){
  629. if (isset($rawData['_id']) === true){
  630. unset($rawData['_id']);
  631. }
  632. $result = $this->getCollection()->update(
  633. array('_id' => $this->_id),
  634. array('$set' => $rawData),
  635. array(
  636. 'fsync'=>$this->getFsyncFlag(),
  637. 'w'=>$this->getSafeFlag(),
  638. 'multiple'=>false
  639. )
  640. );
  641. } else {
  642. if (isset($rawData['_id']) === true){
  643. unset($rawData['_id']);
  644. }
  645. $update = array('$set' => $rawData);
  646. if (!empty($unset_arr)){
  647. $update['$unset'] = $unset_arr;
  648. }
  649. $result = $this->getCollection()->update(
  650. array('_id' => $this->_id),
  651. $update,
  652. array(
  653. 'fsync'=>$this->getFsyncFlag(),
  654. 'w'=>$this->getSafeFlag(),
  655. 'multiple'=>false
  656. )
  657. );
  658. }
  659. if($result !== false) // strict comparison needed
  660. {
  661. $this->afterSave();
  662. return true;
  663. }
  664. throw new CException(Yii::t('yii', 'Can\t save document to disk, or try to save empty document!'));
  665. }
  666. }
  667. /**
  668. * Atomic, in-place update method.
  669. *
  670. * @since v1.3.6
  671. * @param EMongoModifier $modifier updating rules to apply
  672. * @param EMongoCriteria $criteria condition to limit updating rules
  673. * @return bool
  674. */
  675. public function updateAll($modifier, $criteria=null) {
  676. Yii::trace(get_class($this).'.updateAll()','ext.MongoDb.EMongoDocument');
  677. if($modifier->canApply === true)
  678. {
  679. $this->applyScopes($criteria);
  680. if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
  681. $result = $this->getCollection()->update($criteria->getConditions(), $modifier->getModifiers(), array(
  682. 'fsync'=>$this->getFsyncFlag(),
  683. 'safe'=>$this->getSafeFlag(),
  684. 'upsert'=>false,
  685. 'multiple'=>true
  686. ));
  687. else
  688. $result = $this->getCollection()->update($criteria->getConditions(), $modifier->getModifiers(), array(
  689. 'upsert'=>false,
  690. 'multiple'=>true
  691. ));
  692. return $result;
  693. } else {
  694. return false;
  695. }
  696. }
  697. /**
  698. * Deletes the row corresponding to this EMongoDocument.
  699. * @return boolean whether the deletion is successful.
  700. * @throws CException if the record is new
  701. * @since v1.0
  702. */
  703. public function delete()
  704. {
  705. if(!$this->getIsNewRecord())
  706. {
  707. Yii::trace(get_class($this).'.delete()','ext.MongoDb.EMongoDocument');
  708. if($this->beforeDelete())
  709. {
  710. $result = $this->deleteByPk($this->getPrimaryKey());
  711. if($result !== false)
  712. {
  713. $this->afterDelete();
  714. $this->setIsNewRecord(true);
  715. return true;
  716. }
  717. else
  718. return false;
  719. }
  720. else
  721. return false;
  722. }
  723. else
  724. throw new CDbException(Yii::t('yii','The EMongoDocument cannot be deleted because it is new.'));
  725. }
  726. /**
  727. * Deletes document with the specified primary key.
  728. * See {@link find()} for detailed explanation about $condition and $params.
  729. * @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).
  730. * @param array|EMongoCriteria $condition query criteria.
  731. * @since v1.0
  732. */
  733. public function deleteByPk($pk, $criteria=null)
  734. {
  735. Yii::trace(get_class($this).'.deleteByPk()','ext.MongoDb.EMongoDocument');
  736. $this->applyScopes($criteria);
  737. $criteria->mergeWith($this->createPkCriteria($pk));
  738. if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
  739. $result = $this->getCollection()->remove($criteria->getConditions(), array(
  740. 'justOne'=>true,
  741. 'fsync'=>$this->getFsyncFlag(),
  742. 'w'=>$this->getSafeFlag()
  743. ));
  744. else
  745. $result = $this->getCollection()->remove($criteria->getConditions(), true);
  746. return $result;
  747. }
  748. /**
  749. * Repopulates this active record with the latest data.
  750. * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
  751. * @since v1.0
  752. */
  753. public function refresh()
  754. {
  755. Yii::trace(get_class($this).'.refresh()','ext.MongoDb.EMongoDocument');
  756. if(!$this->getIsNewRecord() && $this->getCollection()->count(array('_id'=>$this->_id))==1)
  757. {
  758. $this->setAttributes($this->getCollection()->find(array('_id'=>$this->_id)), false);
  759. return true;
  760. }
  761. else
  762. return false;
  763. }
  764. /**
  765. * Finds a single EMongoDocument with the specified condition.
  766. * @param array|EMongoCriteria $condition query criteria.
  767. *
  768. * If an array, it is treated as the initial values for constructing a {@link EMongoCriteria} object;
  769. * Otherwise, it should be an instance of {@link EMongoCriteria}.
  770. *
  771. * @return EMongoDocument the record found. Null if no record is found.
  772. * @since v1.0
  773. */
  774. public function find($criteria=null)
  775. {
  776. Yii::trace(get_class($this).'.find()','ext.MongoDb.EMongoDocument');
  777. if($this->beforeFind())
  778. {
  779. $this->applyScopes($criteria);
  780. $doc = $this->getCollection()->findOne($criteria->getConditions(), $criteria->getSelect());
  781. return $this->populateRecord($doc);
  782. }
  783. return null;
  784. }
  785. /**
  786. * Finds all documents satisfying the specified condition.
  787. * See {@link find()} for detailed explanation about $condition and $params.
  788. * @param array|EMongoCriteria $condition query criteria.
  789. * @return array list of documents satisfying the specified condition. An empty array is returned if none is found.
  790. * @since v1.0
  791. */
  792. public function findAll($criteria=null)
  793. {
  794. Yii::trace(get_class($this).'.findAll()','ext.MongoDb.EMongoDocument');
  795. if($this->beforeFind())
  796. {
  797. $this->applyScopes($criteria);
  798. $cursor = $this->getCollection()->find($criteria->getConditions());
  799. if($criteria->getSort() !== null)
  800. $cursor->sort($criteria->getSort());
  801. if($criteria->getLimit() !== null)
  802. $cursor->limit($criteria->getLimit());
  803. if($criteria->getOffset() !== null)
  804. $cursor->skip($criteria->getOffset());
  805. if($criteria->getSelect())
  806. $cursor->fields($criteria->getSelect(true));
  807. if($this->getUseCursor($criteria))
  808. return new EMongoCursor($cursor, $this->model());
  809. else
  810. return $this->populateRecords($cursor);
  811. }
  812. return array();
  813. }
  814. /**
  815. * Finds document with the specified primary key.
  816. * In MongoDB world every document has '_id' unique field, so with this method that
  817. * field is in use as PK!
  818. * See {@link find()} for detailed explanation about $condition.
  819. * @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).
  820. * @param array|EMongoCriteria $condition query criteria.
  821. * @return the document found. An null is returned if none is found.
  822. * @since v1.0
  823. */
  824. public function findByPk($pk, $criteria=null)
  825. {
  826. Yii::trace(get_class($this).'.findByPk()','ext.MongoDb.EMongoDocument');
  827. $criteria = new EMongoCriteria($criteria);
  828. $criteria->mergeWith($this->createPkCriteria($pk));
  829. return $this->find($criteria);
  830. }
  831. /**
  832. * Finds all documents with the specified primary keys.
  833. * In MongoDB world every document has '_id' unique field, so with this method that
  834. * field is in use as PK by default.
  835. * See {@link find()} for detailed explanation about $condition.
  836. * @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).
  837. * @param array|EMongoCriteria $condition query criteria.
  838. * @return the document found. An null is returned if none is found.
  839. * @since v1.0
  840. */
  841. public function findAllByPk($pk, $criteria=null)
  842. {
  843. Yii::trace(get_class($this).'.findAllByPk()','ext.MongoDb.EMongoDocument');
  844. $criteria = new EMongoCriteria($criteria);
  845. $criteria->mergeWith($this->createPkCriteria($pk, true));
  846. return $this->findAll($criteria);
  847. }
  848. /**
  849. * Finds document with the specified attributes.
  850. *
  851. * See {@link find()} for detailed explanation about $condition.
  852. * @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).
  853. * @param array|EMongoCriteria $condition query criteria.
  854. * @return the document found. An null is returned if none is found.
  855. * @since v1.0
  856. */
  857. public function findByAttributes(array $attributes)
  858. {
  859. $criteria = new EMongoCriteria();
  860. foreach($attributes as $name=>$value)
  861. {
  862. $criteria->$name('==', $value);
  863. }
  864. return $this->find($criteria);
  865. }
  866. /**
  867. * Finds all documents with the specified attributes.
  868. *
  869. * See {@link find()} for detailed explanation about $condition.
  870. * @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).
  871. * @param array|EMongoCriteria $condition query criteria.
  872. * @return the document found. An null is returned if none is found.
  873. * @since v1.0
  874. */
  875. public function findAllByAttributes(array $attributes)
  876. {
  877. $criteria = new EMongoCriteria();
  878. foreach($attributes as $name=>$value)
  879. {
  880. $criteria->$name('==', $value);
  881. }
  882. return $this->findAll($criteria);
  883. }
  884. /**
  885. * Counts all documents satisfying the specified condition.
  886. * See {@link find()} for detailed explanation about $condition and $params.
  887. * @param array|EMongoCriteria $condition query criteria.
  888. * @return integer Count of all documents satisfying the specified condition.
  889. * @since v1.0
  890. */
  891. public function count($criteria=null)
  892. {
  893. Yii::trace(get_class($this).'.count()','ext.MongoDb.EMongoDocument');
  894. $this->applyScopes($criteria);
  895. return $this->getCollection()->count($criteria->getConditions());
  896. }
  897. /**
  898. * Counts all documents satisfying the specified condition.
  899. * See {@link find()} for detailed explanation about $condition and $params.
  900. * @param array|EMongoCriteria $condition query criteria.
  901. * @return integer Count of all documents satisfying the specified condition.
  902. * @since v1.2.2
  903. */
  904. public function countByAttributes(array $attributes)
  905. {
  906. Yii::trace(get_class($this).'.countByAttributes()','ext.MongoDb.EMongoDocument');
  907. $criteria = new EMongoCriteria;
  908. foreach($attributes as $name=>$value)
  909. $criteria->$name = $value;
  910. $this->applyScopes($criteria);
  911. return $this->getCollection()->count($criteria->getConditions());
  912. }
  913. /**
  914. * Deletes documents with the specified primary keys.
  915. * See {@link find()} for detailed explanation about $condition and $params.
  916. * @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).
  917. * @param array|EMongoCriteria $condition query criteria.
  918. * @since v1.0
  919. */
  920. public function deleteAll($criteria=null)
  921. {
  922. Yii::trace(get_class($this).'.deleteByPk()','ext.MongoDb.EMongoDocument');
  923. $this->applyScopes($criteria);
  924. if(version_compare(Mongo::VERSION, '1.0.5','>=') === true)
  925. return $this->getCollection()->remove($criteria->getConditions(), array(
  926. 'justOne'=>false,
  927. 'fsync'=>$this->getFsyncFlag(),
  928. 'safe'=>$this->getSafeFlag()
  929. ));
  930. else
  931. return $this->getCollection()->remove($criteria->getConditions(), false);
  932. }
  933. /**
  934. * This event is raised before the record is saved.
  935. * By setting {@link CModelEvent::isValid} to be false, the normal {@link save()} process will be stopped.
  936. * @param CModelEvent $event the event parameter
  937. * @since v1.0
  938. */
  939. public function onBeforeSave($event)
  940. {
  941. $this->raiseEvent('onBeforeSave',$event);
  942. }
  943. /**
  944. * This event is raised after the record is saved.
  945. * @param CEvent $event the event parameter
  946. * @since v1.0
  947. */
  948. public function onAfterSave($event)
  949. {
  950. $this->raiseEvent('onAfterSave',$event);
  951. }
  952. /**
  953. * This event is raised before the record is deleted.
  954. * By setting {@link CModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped.
  955. * @param CModelEvent $event the event parameter
  956. * @since v1.0
  957. */
  958. public function onBeforeDelete($event)
  959. {
  960. $this->raiseEvent('onBeforeDelete',$event);
  961. }
  962. /**
  963. * This event is raised after the record is deleted.
  964. * @param CEvent $event the event parameter
  965. * @since v1.0
  966. */
  967. public function onAfterDelete($event)
  968. {
  969. $this->raiseEvent('onAfterDelete',$event);
  970. }
  971. /**
  972. * This event is raised before finder performs a find call.
  973. * In this event, the {@link CModelEvent::criteria} property contains the query criteria
  974. * passed as parameters to those find methods. If you want to access
  975. * the query criteria specified in scopes, please use {@link getDbCriteria()}.
  976. * You can modify either criteria to customize them based on needs.
  977. * @param CModelEvent $event the event parameter
  978. * @see beforeFind
  979. * @since v1.0
  980. */
  981. public function onBeforeFind($event)
  982. {
  983. $this->raiseEvent('onBeforeFind',$event);
  984. }
  985. /**
  986. * This event is raised after the record is instantiated by a find method.
  987. * @param CEvent $event the event parameter
  988. * @since v1.0
  989. */
  990. public function onAfterFind($event)
  991. {
  992. $this->raiseEvent('onAfterFind',$event);
  993. }
  994. /**
  995. * This method is invoked before saving a record (after validation, if any).
  996. * The default implementation raises the {@link onBeforeSave} event.
  997. * You may override this method to do any preparation work for record saving.
  998. * Use {@link isNewRecord} to determine whether the saving is
  999. * for inserting or updating record.
  1000. * Make sure you call the parent implementation so that the event is raised properly.
  1001. * @return boolean whether the saving should be executed. Defaults to true.
  1002. * @since v1.0
  1003. */
  1004. protected function beforeSave()
  1005. {
  1006. if($this->hasEventHandler('onBeforeSave'))
  1007. {
  1008. $event=new CModelEvent($this);
  1009. $this->onBeforeSave($event);
  1010. return $event->isValid;
  1011. }
  1012. else
  1013. return true;
  1014. }
  1015. /**
  1016. * This method is invoked after saving a record successfully.
  1017. * The default implementation raises the {@link onAfterSave} event.
  1018. * You may override this method to do postprocessing after record saving.
  1019. * Make sure you call the parent implementation so that the event is raised properly.
  1020. * @since v1.0
  1021. */
  1022. protected function afterSave()
  1023. {
  1024. if($this->hasEventHandler('onAfterSave'))
  1025. $this->onAfterSave(new CEvent($this));
  1026. }
  1027. /**
  1028. * This method is invoked before deleting a record.
  1029. * The default implementation raises the {@link onBeforeDelete} event.
  1030. * You may override this method to do any preparation work for record deletion.
  1031. * Make sure you call the parent implementation so that the event is raised properly.
  1032. * @return boolean whether the record should be deleted. Defaults to true.
  1033. * @since v1.0
  1034. */
  1035. protected function beforeDelete()
  1036. {
  1037. if($this->hasEventHandler('onBeforeDelete'))
  1038. {
  1039. $event=new CModelEvent($this);
  1040. $this->onBeforeDelete($event);
  1041. return $event->isValid;
  1042. }
  1043. else
  1044. return true;
  1045. }
  1046. /**
  1047. * This method is invoked after deleting a record.
  1048. * The default implementation raises the {@link onAfterDelete} event.
  1049. * You may override this method to do postprocessing after the record is deleted.
  1050. * Make sure you call the parent implementation so that the event is raised properly.
  1051. * @since v1.0
  1052. */
  1053. protected function afterDelete()
  1054. {
  1055. if($this->hasEventHandler('onAfterDelete'))
  1056. $this->onAfterDelete(new CEvent($this));
  1057. }
  1058. /**
  1059. * This method is invoked before an AR finder executes a find call.
  1060. * The find calls include {@link find}, {@link findAll}, {@link findByPk},
  1061. * {@link findAllByPk}, {@link findByAttributes} and {@link findAllByAttributes}.
  1062. * The default implementation raises the {@link onBeforeFind} event.
  1063. * If you override this method, make sure you call the parent implementation
  1064. * so that the event is raised properly.
  1065. *
  1066. * Starting from version 1.1.5, this method may be called with a hidden {@link CDbCriteria}
  1067. * parameter which represents the current query criteria as passed to a find method of AR.
  1068. * @since v1.0
  1069. */
  1070. protected function beforeFind()
  1071. {
  1072. if($this->hasEventHandler('onBeforeFind'))
  1073. {
  1074. $event=new CModelEvent($this);
  1075. $this->onBeforeFind($event);
  1076. return $event->isValid;
  1077. }
  1078. else
  1079. return true;
  1080. }
  1081. /**
  1082. * This method is invoked after each record is instantiated by a find method.
  1083. * The default implementation raises the {@link onAfterFind} event.
  1084. * You may override this method to do postprocessing after each newly found record is instantiated.
  1085. * Make sure you call the parent implementation so that the event is raised properly.
  1086. * @since v1.0
  1087. */
  1088. protected function afterFind()
  1089. {
  1090. if($this->hasEventHandler('onAfterFind'))
  1091. $this->onAfterFind(new CEvent($this));
  1092. }
  1093. /**
  1094. * Creates an document instance.
  1095. * This method is called by {@link populateRecord} and {@link populateRecords}.
  1096. * You may override this method if the instance being created
  1097. * depends the attributes that are to be populated to the record.
  1098. * @param array $attributes list of attribute values for the active records.
  1099. * @return EMongoDocument the document
  1100. * @since v1.0
  1101. */
  1102. protected function instantiate($attributes)
  1103. {
  1104. $class=get_class($this);
  1105. $model=new $class(null);
  1106. $model->initEmbeddedDocuments();
  1107. $model->setAttributes($attributes, false);
  1108. return $model;
  1109. }
  1110. /**
  1111. * Creates an EMongoDocument with the given attributes.
  1112. * This method is internally used by the find methods.
  1113. * @param array $attributes attribute values (column name=>column value)
  1114. * @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated.
  1115. * This parameter is added in version 1.0.3.
  1116. * @return EMongoDocument the newly created document. The class of the object is the same as the model class.
  1117. * Null is returned if the input data is false.
  1118. * @since v1.0
  1119. */
  1120. public function populateRecord($document, $callAfterFind=true)
  1121. {
  1122. if($document!==null)
  1123. {
  1124. $model=$this->instantiate($document);
  1125. $model->setScenario('update');
  1126. $model->init();
  1127. $model->attachBehaviors($model->behaviors());
  1128. if($callAfterFind)
  1129. $model->afterFind();
  1130. return $model;
  1131. }
  1132. else
  1133. return null;
  1134. }
  1135. /**
  1136. * Creates a list of documents based on the input data.
  1137. * This method is internally used by the find methods.
  1138. * @param array $data list of attribute values for the active records.
  1139. * @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated.
  1140. * This parameter is added in version 1.0.3.
  1141. * @param string $index the name of the attribute whose value will be used as indexes of the query result array.
  1142. * If null, it means the array will be indexed by zero-based integers.
  1143. * @return array list of active records.
  1144. * @since v1.0
  1145. */
  1146. public function populateRecords($data, $callAfterFind=true, $index=null)
  1147. {
  1148. $records=array();
  1149. foreach($data as $attributes)
  1150. {
  1151. if(($record=$this->populateRecord($attributes,$callAfterFind))!==null)
  1152. {
  1153. if($index===null)
  1154. $records[]=$record;
  1155. else
  1156. $records[$record->$index]=$record;
  1157. }
  1158. }
  1159. return $records;
  1160. }
  1161. /**
  1162. * Magic search method, provides basic search functionality.
  1163. *
  1164. * Returns EMongoDocument object ($this) with criteria set to
  1165. * rexexp: /$attributeValue/i
  1166. * used for Data provider search functionality
  1167. * @param boolean $caseSensitive whathever do a case-sensitive search, default to false
  1168. * @return EMongoDocument
  1169. * @since v1.2.2
  1170. */
  1171. public function search($caseSensitive = false)
  1172. {
  1173. $criteria = $this->getDbCriteria();
  1174. foreach($this->getSafeAttributeNames() as $attribute)
  1175. {
  1176. if($this->$attribute !== null && $this->$attribute !== '')
  1177. {
  1178. if(is_array($this->$attribute) || is_object($this->$attribute))
  1179. $criteria->$attribute = $this->$attribute;
  1180. else if(preg_match('/^(?:\s*(<>|<=|>=|<|>|=|!=|==))?(.*)$/',$this->$attribute,$matches))
  1181. {
  1182. $op = $matches[1];
  1183. $value = $matches[2];
  1184. if($op === '=') $op = '==';
  1185. if($op !== '')
  1186. call_user_func(array($criteria, $attribute), $op, is_numeric($value) ? floatval($value) : $value);
  1187. else
  1188. $criteria->$attribute = new MongoRegex($caseSensitive ? '/'.$this->$attribute.'/' : '/'.$this->$attribute.'/i');
  1189. }
  1190. }
  1191. }
  1192. $this->setDbCriteria($criteria);
  1193. return new EMongoDocumentDataProvider($this);
  1194. }
  1195. /**
  1196. * Returns the static model of the specified EMongoDocument class.
  1197. * The model returned is a static instance of the EMongoDocument class.
  1198. * It is provided for invoking class-level methods (something similar to static class methods.)
  1199. *
  1200. * EVERY derived EMongoDocument class must override this method as follows,
  1201. * <pre>
  1202. * public static function model($className=__CLASS__)
  1203. * {
  1204. * return parent::model($className);
  1205. * }
  1206. * </pre>
  1207. *
  1208. * @param string $className EMongoDocument class name.
  1209. * @return EMongoDocument EMongoDocument model instance.
  1210. * @since v1.0
  1211. */
  1212. public static function model($className=__CLASS__)
  1213. {
  1214. if(isset(self::$_models[$className]))
  1215. return self::$_models[$className];
  1216. else
  1217. {
  1218. $model=self::$_models[$className]=new $className(null);
  1219. $model->attachBehaviors($model->behaviors());
  1220. return $model;
  1221. }
  1222. }
  1223. /**
  1224. * @since v1.2.2
  1225. */
  1226. private function createPkCriteria($pk, $multiple=false)
  1227. {
  1228. $pkField = $this->primaryKey();
  1229. $criteria = new EMongoCriteria();
  1230. if(is_string($pkField))
  1231. {
  1232. if(!$multiple)
  1233. $criteria->{$pkField} = $pk;
  1234. else
  1235. $criteria->{$pkField}('in', $pk);
  1236. }
  1237. else if(is_array($pkField))
  1238. {
  1239. if(!$multiple)
  1240. for($i=0; $i<count($pkField); $i++)
  1241. $criteria->{$pkField[$i]} = $pk[$i];
  1242. else
  1243. throw new EMongoException(Yii::t('yii', 'Cannot create PK criteria for multiple composite key\'s (not implemented yet)'));
  1244. }
  1245. return $criteria;
  1246. }
  1247. }