ActiveQuery.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <?php
  2. /**
  3. * xunsearch ActiveQuery 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\base\Component;
  14. use yii\db\ActiveQueryInterface;
  15. use yii\db\ActiveQueryTrait;
  16. use yii\db\ActiveRelationTrait;
  17. use yii\db\QueryTrait;
  18. /**
  19. * ActiveQuery represents a XS query associated with an ActiveRecord class.
  20. *
  21. * An ActiveQuery can be a normal query or be used in a relational context.
  22. *
  23. * ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findByQuery()]].
  24. * Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
  25. *
  26. * Normal Query
  27. * ------------
  28. *
  29. * ActiveQuery mainly provides the following methods to retrieve the query results:
  30. *
  31. * - [[one()]]: returns a single record populated with the first row of data.
  32. * - [[all()]]: returns all records based on the query results.
  33. * - [[count()]]: returns the number of records.
  34. * - [[exists()]]: returns a value indicating whether the query result has data or not.
  35. *
  36. * Because ActiveQuery use [[QueryTrait]], one can use query methods, such as [[where()]],
  37. * [[orderBy()]] to customize the query options.
  38. *
  39. * ActiveQuery also provides the following additional query options:
  40. *
  41. * - [[with()]]: list of relations that this query should be performed with.
  42. * - [[indexBy()]]: the name of the column by which the query result should be indexed.
  43. * - [[asArray()]]: whether to return each record as an array.
  44. *
  45. * These options can be configured using methods of the same name. For example:
  46. *
  47. * ```php
  48. * $docs = Demo::find()->with('orders')->asArray()->all();
  49. * ```
  50. *
  51. * Relational query
  52. * ----------------
  53. *
  54. * In relational context ActiveQuery represents a relation between two Active Record classes.
  55. *
  56. * Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
  57. * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
  58. * a getter method which calls one of the above methods and returns the created ActiveQuery object.
  59. *
  60. * A relation is specified by [[link]] which represents the association between columns
  61. * of different tables; and the multiplicity of the relation is indicated by [[multiple]].
  62. *
  63. * If a relation involves a junction table, it may be specified by [[via()]] or [[viaTable()]] method.
  64. * These methods may only be called in a relational context. Same is true for [[inverseOf()]], which
  65. * marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that
  66. * is to be added to relational query join condition.
  67. *
  68. * @property-read Database $db default database to use
  69. * @property-read \XSSearch $search
  70. *
  71. * @author xjflyttp <xjflyttp@gmail.com>
  72. * @author hightman <hightman@twomice.net>
  73. * @since 1.4.9
  74. */
  75. class ActiveQuery extends Component implements ActiveQueryInterface
  76. {
  77. use ActiveQueryTrait;
  78. use ActiveRelationTrait;
  79. use QueryTrait;
  80. /**
  81. * @event Event an event that is triggered when the query is initialized via [[init()]].
  82. */
  83. const EVENT_INIT = 'init';
  84. const EVENT_BEFORE_SEARCH = 'beforeSearch';
  85. /**
  86. * @var string the query string, this is set by [[ActiveRecord::findByQuery()]].
  87. */
  88. public $query;
  89. /**
  90. * @var boolean fuzzy search
  91. * @see http://www.xunsearch.com/doc/php/api/XSSearch#setFuzzy-detail
  92. */
  93. public $fuzzy = false;
  94. /**
  95. * @var boolean expand synonyms automatically
  96. * @see http://www.xunsearch.com/doc/php/api/XSSearch#setAutoSynonyms-detail
  97. */
  98. public $synonyms = false;
  99. /**
  100. * @var callable
  101. */
  102. public $buildOther;
  103. /**
  104. * @var \XSSearch
  105. */
  106. private $_search;
  107. /**
  108. * Constructor.
  109. * @param array $modelClass the model class associated with this query
  110. * @param array $config configurations to be applied to the newly created query object
  111. */
  112. public function __construct($modelClass, $config = [])
  113. {
  114. $this->modelClass = $modelClass;
  115. parent::__construct($config);
  116. }
  117. /**
  118. * Initializes the object.
  119. * This method is called at the end of the constructor. The default implementation will trigger
  120. * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
  121. * to ensure triggering of the event.
  122. */
  123. public function init()
  124. {
  125. parent::init();
  126. $this->trigger(self::EVENT_INIT);
  127. }
  128. /**
  129. * @return Database default xunsearch database
  130. */
  131. public function getDb()
  132. {
  133. $modelClass = $this->modelClass;
  134. return $modelClass::getDb();
  135. }
  136. /**
  137. * return \XSSearch current XS search object
  138. */
  139. public function getSearch()
  140. {
  141. return $this->_search;
  142. }
  143. /**
  144. * Enable fuzzy search
  145. * @param boolean $fuzzy
  146. * @return static the query object itself.
  147. */
  148. public function fuzzy($fuzzy = true)
  149. {
  150. $this->fuzzy = $fuzzy === true;
  151. return $this;
  152. }
  153. /**
  154. * Enable synonyms search
  155. * @param boolean $synonyms
  156. * @return static the query object itself.
  157. */
  158. public function synonyms($synonyms = true)
  159. {
  160. $this->synonyms = $synonyms === true;
  161. return $this;
  162. }
  163. /**
  164. * Build other search options, such as weight, collapse etc.
  165. *
  166. * ```php
  167. * $finder = Demo::find();
  168. * $finder->where('hello')->buildOther(function(\XSSearch $search) {
  169. * $search->addWeight('subject', 'hi', 1);
  170. * })->asArray()->all();
  171. * ```
  172. * @param callable $callable a PHP callable that contains setting before searching
  173. * @return static the query object itself.
  174. */
  175. public function buildOther(callable $callable)
  176. {
  177. $this->buildOther = $callable;
  178. return $this;
  179. }
  180. /**
  181. * @param weight
  182. */
  183. protected function beforeSearch()
  184. {
  185. $this->trigger(self::EVENT_BEFORE_SEARCH);
  186. }
  187. /**
  188. * Executes query and returns all results as an array.
  189. * @param Database $db the database used to execute the query.
  190. * If null, the DB returned by [[modelClass]] will be used.
  191. * @return array|ActiveRecord[] the search results. If the results in nothing, an empty array will be returned.
  192. */
  193. public function all($db = null)
  194. {
  195. $query = $this->query;
  196. $search = $this->buildSearch($db);
  197. $this->beforeSearch();
  198. $profile = $db->getName() . '.findAll#' . $this->query;
  199. Yii::beginProfile($profile, __METHOD__);
  200. $docs = $search->search($query);
  201. Yii::endProfile($profile, __METHOD__);
  202. return $this->populate($docs);
  203. }
  204. /**
  205. * Executes query and returns a single row of result.
  206. * @param Database $db the database used to execute the query.
  207. * If null, the DB returned by [[modelClass]] will be used.
  208. * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
  209. * the query result may be either an array or an ActiveRecord object. Null will be returned
  210. * if the query results in nothing.
  211. */
  212. public function one($db = null)
  213. {
  214. $query = $this->query;
  215. $search = $this->buildSearch($db)->setLimit(1);
  216. $this->beforeSearch();
  217. $profile = $db->getName() . '.findOne#' . $this->query;
  218. Yii::beginProfile($profile, __METHOD__);
  219. $docs = $search->search($query);
  220. Yii::endProfile($profile, __METHOD__);
  221. $models = $this->populate($docs);
  222. if (count($models) === 0) {
  223. return null;
  224. } else {
  225. return $models[0];
  226. }
  227. }
  228. /**
  229. * Returns the number of records.
  230. * @param string $q the COUNT query. Defaults to '*'.
  231. * @param Database $db the database used to execute the query.
  232. * If null, the DB returned by [[modelClass]] will be used.
  233. * @return integer number of records
  234. */
  235. public function count($q = '*', $db = null)
  236. {
  237. if ($q !== '*') {
  238. $this->query = $q;
  239. }
  240. $query = $this->query;
  241. $search = $this->buildSearch($db);
  242. $profile = $db->getName() . '.count#' . $this->query;
  243. Yii::beginProfile($profile, __METHOD__);
  244. $count = $search->count($query);
  245. Yii::endProfile($profile, __METHOD__);
  246. return $count;
  247. }
  248. /**
  249. * Returns a value indicating whether the query result contains any row of data.
  250. * @param Database $db the database connection used to execute the query.
  251. * @return boolean whether the query result contains any row of data.
  252. */
  253. public function exists($db = null)
  254. {
  255. return $this->one($db) !== null;
  256. }
  257. /**
  258. * @inheritdoc
  259. */
  260. public function where($condition)
  261. {
  262. $this->query = null;
  263. $this->where = $condition;
  264. return $this;
  265. }
  266. /**
  267. * Converts the found docs into the format as specified by this query.
  268. * @param \XSDocument[] $docs the raw query result from database
  269. * @return array|ActiveRecord[] the converted query result
  270. */
  271. private function populate($docs)
  272. {
  273. if (empty($docs)) {
  274. return [];
  275. }
  276. $models = [];
  277. $class = $this->modelClass;
  278. foreach ($docs as $doc) {
  279. if ($this->asArray) {
  280. $model = $doc->getFields();
  281. /*
  282. $model['__docid'] = $doc->docid();
  283. $model['__percent'] = $doc->percent();
  284. $model['__weight'] = $doc->weight();
  285. $model['__ccount'] = $doc->ccount();
  286. $model['__matched'] = $doc->matched();
  287. */
  288. } else {
  289. $model = $class::instantiate($doc);
  290. $class::populateRecord($model, $doc);
  291. }
  292. if ($this->indexBy === null) {
  293. $models[] = $model;
  294. } else {
  295. if (is_string($this->indexBy)) {
  296. $key = $doc[$this->indexBy];
  297. } else {
  298. $key = call_user_func($this->indexBy, $model);
  299. }
  300. $models[$key] = $model;
  301. }
  302. }
  303. if (!empty($this->with)) {
  304. $this->findWith($this->with, $models);
  305. }
  306. if (!$this->asArray) {
  307. foreach ($models as $model) {
  308. $model->afterFind();
  309. }
  310. }
  311. return $models;
  312. }
  313. /**
  314. * Prepare for searching and build it
  315. * @param Database $db the database used to perform search.
  316. * @return \XSSearch ready XS search object
  317. */
  318. private function buildSearch(&$db)
  319. {
  320. if ($db === null) {
  321. $db = $this->getDb();
  322. }
  323. return $this->_search = $db->getQueryBuilder()->build($this);
  324. }
  325. }