ActiveRecord.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <?php
  2. /**
  3. * xunsearch ActiveRecord class file
  4. *
  5. * @author hightman
  6. * @link http://www.xunsearch.com/
  7. * @copyright Copyright &copy; 2014 HangZhou YunSheng Network Technology Co., Ltd.
  8. * @license http://www.xunsearch.com/license/
  9. * @version $Id$
  10. */
  11. namespace hightman\xunsearch;
  12. use Yii;
  13. use yii\db\BaseActiveRecord;
  14. use yii\helpers\StringHelper;
  15. /**
  16. * xunsearch AR
  17. *
  18. * @property-write $internalDoc internal XSDocument object of search result
  19. *
  20. * The following magic methods only for AR object returned by ActiveQuery
  21. * @method int docid() docid(void)
  22. * @method int rank() rank(void)
  23. * @method int percent() percent(void)
  24. * @method float weight() weight(void)
  25. * @method int ccount() ccount(void)
  26. * @method array matched() matched(void)
  27. *
  28. * @author xjflyttp <xjflyttp@gmail.com>
  29. * @author hightman <hightman@twomice.net>
  30. * @since 1.4.9
  31. */
  32. class ActiveRecord extends BaseActiveRecord
  33. {
  34. /**
  35. * @var \XSDocument internal document
  36. */
  37. private $_internalDoc;
  38. /**
  39. * @return string XS project name
  40. */
  41. public static function projectName()
  42. {
  43. return strtolower(StringHelper::basename(get_called_class()));
  44. }
  45. /**
  46. * Returns the database used by this AR class.
  47. * By default, the "xunsearch" application component with [[projectName()]] is used to open the database.
  48. * You may override this method if you want to use a different database.
  49. * @return Database
  50. */
  51. public static function getDb()
  52. {
  53. return Yii::$app->get('xunsearch')->getDatabase(static::projectName());
  54. }
  55. /**
  56. * Returns the primary key name(s) for this AR class.
  57. * You need not overridden this method.
  58. *
  59. * @return string[] the primary keys of this record.
  60. */
  61. public static function primaryKey()
  62. {
  63. $db = static::getDb();
  64. return [$db->getFieldId()->name];
  65. }
  66. /**
  67. * @inheritdoc
  68. * @return ActiveQuery
  69. */
  70. public static function find()
  71. {
  72. return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
  73. }
  74. /**
  75. * Creates an [[ActiveQuery]] instance with a given query statement.
  76. * @param string $q the search query statement
  77. * @return ActiveQuery the newly created [[ActiveQuery]] instance
  78. */
  79. public static function findByQuery($q)
  80. {
  81. $query = static::find();
  82. $query->query = $q;
  83. return $query;
  84. }
  85. /**
  86. * Updates the whole database using the provided attribute values and conditions.
  87. * For example, to change the status to be 1 for all customers whose status is 2:
  88. *
  89. * ```php
  90. * Customer::updateAll(['status' => 1], ['status' => '2']);
  91. * ```
  92. *
  93. * @param array $attributes attribute values (name-value pairs) to be saved into the table
  94. * @param string|array $condition the conditions that will be converted to query string
  95. * @return integer the number of rows updated
  96. */
  97. public static function updateAll($attributes, $condition = '')
  98. {
  99. $count = 0;
  100. $records = static::find()->where($condition)->all();
  101. foreach ($records as $record) {
  102. $record->setAttributes($attributes);
  103. if ($record->update() === true) {
  104. $count++;
  105. }
  106. }
  107. return $count;
  108. }
  109. /**
  110. * Deletes rows in the table using the provided conditions.
  111. * WARNING: If you do not specify any condition, this method will delete ALL rows in the database.
  112. *
  113. * For example, to delete all customers whose status is 3:
  114. *
  115. * ```php
  116. * Customer::deleteAll(['status' => 3]);
  117. * ```
  118. *
  119. * @param array $condition the conditions that will be converted to query string.
  120. * @return integer the number of records deleted
  121. */
  122. public static function deleteAll($condition = null)
  123. {
  124. $pks = self::fetchPks($condition);
  125. if (empty($pks)) {
  126. return 0;
  127. }
  128. $db = static::getDb();
  129. $profile = $db->getName() . '.deleteAll#' . implode(',', $pks);
  130. Yii::beginProfile($profile, __METHOD__);
  131. $db->getIndex()->del($pks);
  132. Yii::endProfile($profile, __METHOD__);
  133. return count($pks);
  134. }
  135. /**
  136. * Populates an active record object using a xunsearch result document
  137. *
  138. * @param ActiveRecord $record the record to be populated.
  139. * @param \XSDocument $doc
  140. */
  141. public static function populateRecord($record, $doc)
  142. {
  143. parent::populateRecord($record, $doc->getFields());
  144. $record->setInternalDoc($doc);
  145. }
  146. /**
  147. * @return \XSDocument internal document
  148. */
  149. protected function getInternalDoc()
  150. {
  151. if ($this->_internalDoc === null) {
  152. $this->_internalDoc = static::getDb()->createDoc();
  153. }
  154. return $this->_internalDoc;
  155. }
  156. /**
  157. * @param \XSDocument $doc
  158. */
  159. public function setInternalDoc(\XSDocument $doc)
  160. {
  161. $this->_internalDoc = $doc;
  162. }
  163. /**
  164. * Magic calls for populated AR object.
  165. *
  166. * @param string $name the method name
  167. * @param array $params method parameters
  168. * @throws UnknownMethodException when calling unknown method
  169. * @return mixed the method return value
  170. */
  171. public function __call($name, $params)
  172. {
  173. if ($this->_internalDoc instanceof \XSDocument) {
  174. try {
  175. return call_user_func_array(array($this->_internalDoc, $name), $params);
  176. } catch (\Exception $e) {
  177. }
  178. }
  179. return parent::__call($name, $params);
  180. }
  181. /**
  182. * Returns the list of all attribute names of the record.
  183. * You need not overridden this method.
  184. *
  185. * @return array list of attribute names.
  186. */
  187. public function attributes()
  188. {
  189. $db = static::getDb();
  190. return array_keys($db->getAllFields());
  191. }
  192. /**
  193. * The primary key is required, all others are safe.
  194. * @return array validation rules for attributes.
  195. */
  196. public function rules()
  197. {
  198. return [
  199. [static::primaryKey(), 'required'],
  200. [$this->attributes(), 'safe'],
  201. ];
  202. }
  203. /**
  204. * Add column index term
  205. * @param string $column
  206. * @param string $term
  207. * @param int $wdf
  208. * @see http://www.xunsearch.com/doc/php/api/XSDocument#addIndex-detail
  209. * @return static the query object itself.
  210. */
  211. public function addTerm($column, $term, $wdf = 1)
  212. {
  213. $this->getInternalDoc()->addTerm($column, $term, $wdf);
  214. }
  215. /**
  216. * Add column index text
  217. * @param string $column
  218. * @param string $text
  219. * @see http://www.xunsearch.com/doc/php/api/XSDocument#addTerm-detail
  220. * @return static the query object itself.
  221. */
  222. public function addIndex($column, $text)
  223. {
  224. $this->getInternalDoc()->addIndex($column, $text);
  225. }
  226. /**
  227. * Inserts a row into the associated database table using the attribute values of this record.
  228. *
  229. * This method performs the following steps in order:
  230. *
  231. * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
  232. * fails, it will skip the rest of the steps;
  233. * 2. call [[afterValidate()]] when `$runValidation` is true.
  234. * 3. call [[beforeSave()]]. If the method returns false, it will skip the
  235. * rest of the steps;
  236. * 4. insert the record into database. If this fails, it will skip the rest of the steps;
  237. * 5. call [[afterSave()]];
  238. *
  239. * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
  240. * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
  241. * will be raised by the corresponding methods.
  242. *
  243. * Note: internal implemention is full update for the whole document.
  244. *
  245. * For example, to insert a demo record:
  246. *
  247. * ```php
  248. * $demo = new Demo;
  249. * $demo->pid = 1;
  250. * $demo->subject = 'hello';
  251. * $demo->message = 'the world';
  252. * $demo->insert();
  253. * ```
  254. *
  255. * @param boolean $runValidation whether to perform validation before saving the record.
  256. * If the validation fails, the record will not be inserted into the database.
  257. * @param array $attributes list of attributes that need to be saved. Defaults to null,
  258. * meaning all attributes that are loaded from DB will be saved.
  259. * @return boolean whether the attributes are valid and the record is inserted successfully.
  260. * @throws \Exception in case insert failed.
  261. */
  262. public function insert($runValidation = true, $attributes = null)
  263. {
  264. if ($runValidation && !$this->validate($attributes)) {
  265. return false;
  266. }
  267. if (!$this->beforeSave(true)) {
  268. return false;
  269. }
  270. $db = static::getDb();
  271. $profile = $db->getName() . '.insert#' . $this->getPrimaryKey();
  272. $values = $this->getDirtyAttributes($attributes);
  273. Yii::beginProfile($profile, __METHOD__);
  274. $this->getInternalDoc()->setFields($values);
  275. $db->getIndex()->update($this->getInternalDoc());
  276. Yii::endProfile($profile, __METHOD__);
  277. $changedAttributes = array_fill_keys(array_keys($values), null);
  278. $this->setOldAttributes($values);
  279. $this->afterSave(true, $changedAttributes);
  280. return true;
  281. }
  282. /**
  283. * Saves the changes to this active record into the associated database table.
  284. *
  285. * This method performs the following steps in order:
  286. *
  287. * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
  288. * fails, it will skip the rest of the steps;
  289. * 2. call [[afterValidate()]] when `$runValidation` is true.
  290. * 3. call [[beforeSave()]]. If the method returns false, it will skip the
  291. * rest of the steps;
  292. * 4. save the record into database. If this fails, it will skip the rest of the steps;
  293. * 5. call [[afterSave()]];
  294. *
  295. * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
  296. * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
  297. * will be raised by the corresponding methods.
  298. *
  299. * Note: internal implemention is full update for the whole document.
  300. *
  301. * For example, to update a demo record:
  302. *
  303. * ```php
  304. * $demo = Demo::findOne($id);
  305. * $demo->subject = 'hello';
  306. * $demo->message = 'the world';
  307. * $demo->update();
  308. * ```
  309. *
  310. * @param boolean $runValidation whether to perform validation before saving the record.
  311. * If the validation fails, the record will not be inserted into the database.
  312. * @param array $attributes list of attribute names that need to be saved. Defaults to null,
  313. * meaning all attributes that are loaded from DB will be saved.
  314. * @return boolean whether the attributes are valid and the record is updated successfully.
  315. * @throws \Exception in case update failed.
  316. */
  317. public function update($runValidation = true, $attributes = null)
  318. {
  319. if ($runValidation && !$this->validate($attributes)) {
  320. return false;
  321. }
  322. if (!$this->beforeSave(true)) {
  323. return false;
  324. }
  325. $values = $this->getDirtyAttributes($attributes);
  326. if (empty($values)) {
  327. $this->afterSave(false, $values);
  328. return 0;
  329. }
  330. $db = static::getDb();
  331. $profile = $db->getName() . '.update#' . $this->getPrimaryKey();
  332. Yii::beginProfile($profile, __METHOD__);
  333. $this->getInternalDoc()->setFields($this->getAttributes($attributes));
  334. $db->getIndex()->update($this->getInternalDoc());
  335. Yii::endProfile($profile, __METHOD__);
  336. $changedAttributes = [];
  337. foreach ($values as $name => $value) {
  338. $changedAttributes[$name] = $this->getOldAttribute($name);
  339. $this->setOldAttribute($name, $value);
  340. }
  341. $this->afterSave(false, $changedAttributes);
  342. return true;
  343. }
  344. /**
  345. * Deletes the table row corresponding to this active record.
  346. *
  347. * This method performs the following steps in order:
  348. *
  349. * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
  350. * rest of the steps;
  351. * 2. delete the record from the database;
  352. * 3. call [[afterDelete()]].
  353. *
  354. * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
  355. * will be raised by the corresponding methods.
  356. *
  357. * @return boolean whether the record is removed successfully.
  358. * @throws \Exception in case delete failed.
  359. */
  360. public function delete()
  361. {
  362. if (($result = $this->beforeDelete()) !== false) {
  363. $pk = $this->getPrimaryKey();
  364. $db = static::getDb();
  365. $profile = $db->getName() . '.delete#' . $pk;
  366. Yii::beginProfile($profile, __METHOD__);
  367. $db->getIndex()->del($pk);
  368. Yii::endProfile($profile, __METHOD__);
  369. $this->setOldAttributes(null);
  370. $this->afterDelete();
  371. }
  372. return $result;
  373. }
  374. /**
  375. * @param mixed $condition
  376. * @return array
  377. */
  378. private static function fetchPks($condition)
  379. {
  380. $primaryKey = static::primaryKey();
  381. $records = static::find()->where($condition)->asArray()->all();
  382. $pks = [];
  383. foreach ($records as $record) {
  384. $pk = $record[$primaryKey[0]];
  385. $pks[] = $pk;
  386. }
  387. return $pks;
  388. }
  389. }