ActiveDataProvider.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\elasticsearch;
  8. use yii\base\InvalidCallException;
  9. use yii\base\InvalidConfigException;
  10. use yii\db\ActiveQueryInterface;
  11. /**
  12. * ActiveDataProvider is an enhanced version of [[\yii\data\ActiveDataProvider]] specific to the Elasticsearch.
  13. * It allows to fetch not only rows and total rows count, but full query results including aggregations and so on.
  14. *
  15. * Note: this data provider fetches result models and total count using single Elasticsearch query, so results total
  16. * count will be fetched after pagination limit applying, which eliminates ability to verify if requested page number
  17. * actually exist. Data provider disables [[yii\data\Pagination::$validatePage]] automatically because of this.
  18. *
  19. * @property-read array $aggregations All aggregations results. This property is read-only.
  20. * @property array $queryResults Full query results.
  21. * @property-read array $suggestions All suggestions results. This property is read-only.
  22. *
  23. * @author Paul Klimov <klimov.paul@gmail.com>
  24. * @since 2.0.5
  25. */
  26. class ActiveDataProvider extends \yii\data\ActiveDataProvider
  27. {
  28. /**
  29. * @var array the full query results.
  30. */
  31. private $_queryResults;
  32. /**
  33. * @param array $results full query results
  34. */
  35. public function setQueryResults($results)
  36. {
  37. $this->_queryResults = $results;
  38. }
  39. /**
  40. * @return array full query results
  41. */
  42. public function getQueryResults()
  43. {
  44. if (!is_array($this->_queryResults)) {
  45. $this->prepare();
  46. }
  47. return $this->_queryResults;
  48. }
  49. /**
  50. * @return array all aggregations results
  51. */
  52. public function getAggregations()
  53. {
  54. $results = $this->getQueryResults();
  55. return isset($results['aggregations']) ? $results['aggregations'] : [];
  56. }
  57. /**
  58. * Returns results of the specified aggregation.
  59. * @param string $name aggregation name.
  60. * @return array aggregation results.
  61. * @throws InvalidCallException if query results do not contain the requested aggregation.
  62. */
  63. public function getAggregation($name)
  64. {
  65. $aggregations = $this->getAggregations();
  66. if (!isset($aggregations[$name])) {
  67. throw new InvalidCallException("Aggregation '{$name}' not found.");
  68. }
  69. return $aggregations[$name];
  70. }
  71. /**
  72. * @return array all suggestions results
  73. */
  74. public function getSuggestions()
  75. {
  76. $results = $this->getQueryResults();
  77. return isset($results['suggest']) ? $results['suggest'] : [];
  78. }
  79. /**
  80. * Returns results of the specified suggestion.
  81. * @param string $name suggestion name.
  82. * @return array suggestion results.
  83. * @throws InvalidCallException if query results do not contain the requested suggestion.
  84. */
  85. public function getSuggestion($name)
  86. {
  87. $suggestions = $this->getSuggestions();
  88. if (!isset($suggestions[$name])) {
  89. throw new InvalidCallException("Suggestion '{$name}' not found.");
  90. }
  91. return $suggestions[$name];
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. protected function prepareModels()
  97. {
  98. if (!$this->query instanceof Query) {
  99. throw new InvalidConfigException('The "query" property must be an instance "' . Query::className() . '" or its subclasses.');
  100. }
  101. $query = clone $this->query;
  102. if (($pagination = $this->getPagination()) !== false) {
  103. // pagination fails to validate page number, because total count is unknown at this stage
  104. $pagination->validatePage = false;
  105. $query->limit($pagination->getLimit())->offset($pagination->getOffset());
  106. }
  107. if (($sort = $this->getSort()) !== false) {
  108. $query->addOrderBy($sort->getOrders());
  109. }
  110. if (is_array(($results = $query->search($this->db)))) {
  111. $this->setQueryResults($results);
  112. if ($pagination !== false) {
  113. $pagination->totalCount = $this->getTotalCount();
  114. }
  115. return $results['hits']['hits'];
  116. }
  117. $this->setQueryResults([]);
  118. return [];
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. protected function prepareTotalCount()
  124. {
  125. if (!$this->query instanceof Query) {
  126. throw new InvalidConfigException('The "query" property must be an instance "' . Query::className() . '" or its subclasses.');
  127. }
  128. $results = $this->getQueryResults();
  129. if (isset($results['hits']['total'])) {
  130. return is_array($results['hits']['total']) ? (int) $results['hits']['total']['value'] : (int) $results['hits']['total'];
  131. }
  132. return 0;
  133. }
  134. /**
  135. * {@inheritdoc}
  136. */
  137. protected function prepareKeys($models)
  138. {
  139. $keys = [];
  140. if ($this->key !== null) {
  141. foreach ($models as $model) {
  142. if (is_string($this->key)) {
  143. $keys[] = $model[$this->key];
  144. } else {
  145. $keys[] = call_user_func($this->key, $model);
  146. }
  147. }
  148. return $keys;
  149. } elseif ($this->query instanceof ActiveQueryInterface) {
  150. /* @var $class \yii\elasticsearch\ActiveRecord */
  151. $class = $this->query->modelClass;
  152. foreach ($models as $model) {
  153. $keys[] = $model->primaryKey;
  154. }
  155. return $keys;
  156. } else {
  157. return array_keys($models);
  158. }
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function refresh()
  164. {
  165. parent::refresh();
  166. $this->_queryResults = null;
  167. }
  168. }