1<?php
2
3namespace Elastica;
4
5use Elastica\Exception\InvalidException;
6
7/**
8 * Elastica result set.
9 *
10 * List of all hits that are returned for a search on elasticsearch
11 * Result set implements iterator
12 *
13 * @author Nicolas Ruflin <spam@ruflin.com>
14 */
15class ResultSet implements \Iterator, \Countable, \ArrayAccess
16{
17    /**
18     * Current position.
19     *
20     * @var int Current position
21     */
22    private $_position = 0;
23
24    /**
25     * Query.
26     *
27     * @var Query Query object
28     */
29    private $_query;
30
31    /**
32     * Response.
33     *
34     * @var Response Response object
35     */
36    private $_response;
37
38    /**
39     * Results.
40     *
41     * @var Result[] Results
42     */
43    private $_results = [];
44
45    /**
46     * Constructs ResultSet object.
47     *
48     * @param Response $response Response object
49     * @param Query    $query    Query object
50     * @param Result[] $results
51     */
52    public function __construct(Response $response, Query $query, $results)
53    {
54        $this->_query = $query;
55        $this->_response = $response;
56        $this->_results = $results;
57    }
58
59    /**
60     * Returns all results.
61     *
62     * @return Result[] Results
63     */
64    public function getResults()
65    {
66        return $this->_results;
67    }
68
69    /**
70     * Returns all Documents.
71     *
72     * @return array Documents \Elastica\Document
73     */
74    public function getDocuments()
75    {
76        $documents = [];
77        foreach ($this->_results as $doc) {
78            $documents[] = $doc->getDocument();
79        }
80
81        return $documents;
82    }
83
84    /**
85     * Returns true if the response contains suggestion results; false otherwise.
86     *
87     * @return bool
88     */
89    public function hasSuggests()
90    {
91        $data = $this->_response->getData();
92
93        return isset($data['suggest']);
94    }
95
96    /**
97     * Return all suggests.
98     *
99     * @return array suggest results
100     */
101    public function getSuggests()
102    {
103        $data = $this->_response->getData();
104
105        return $data['suggest'] ?? [];
106    }
107
108    /**
109     * Returns whether aggregations exist.
110     *
111     * @return bool Aggregation existence
112     */
113    public function hasAggregations()
114    {
115        $data = $this->_response->getData();
116
117        return isset($data['aggregations']);
118    }
119
120    /**
121     * Returns all aggregation results.
122     *
123     * @return array
124     */
125    public function getAggregations()
126    {
127        $data = $this->_response->getData();
128
129        return $data['aggregations'] ?? [];
130    }
131
132    /**
133     * Retrieve a specific aggregation from this result set.
134     *
135     * @param string $name the name of the desired aggregation
136     *
137     * @throws Exception\InvalidException if an aggregation by the given name cannot be found
138     *
139     * @return array
140     */
141    public function getAggregation($name)
142    {
143        $data = $this->_response->getData();
144
145        if (isset($data['aggregations']) && isset($data['aggregations'][$name])) {
146            return $data['aggregations'][$name];
147        }
148        throw new InvalidException("This result set does not contain an aggregation named {$name}.");
149    }
150
151    /**
152     * Returns the total number of found hits.
153     *
154     * @return int Total hits
155     */
156    public function getTotalHits()
157    {
158        $data = $this->_response->getData();
159
160        return (int) ($data['hits']['total']['value'] ?? 0);
161    }
162
163    /**
164     * Returns the max score of the results found.
165     *
166     * @return float Max Score
167     */
168    public function getMaxScore()
169    {
170        $data = $this->_response->getData();
171
172        return (float) ($data['hits']['max_score'] ?? 0);
173    }
174
175    /**
176     * Returns the total number of ms for this search to complete.
177     *
178     * @return int Total time
179     */
180    public function getTotalTime()
181    {
182        $data = $this->_response->getData();
183
184        return $data['took'] ?? 0;
185    }
186
187    /**
188     * Returns true if the query has timed out.
189     *
190     * @return bool Timed out
191     */
192    public function hasTimedOut()
193    {
194        $data = $this->_response->getData();
195
196        return !empty($data['timed_out']);
197    }
198
199    /**
200     * Returns response object.
201     *
202     * @return Response Response object
203     */
204    public function getResponse()
205    {
206        return $this->_response;
207    }
208
209    /**
210     * @return Query
211     */
212    public function getQuery()
213    {
214        return $this->_query;
215    }
216
217    /**
218     * Returns size of current set.
219     *
220     * @return int Size of set
221     */
222    public function count()
223    {
224        return \count($this->_results);
225    }
226
227    /**
228     * Returns size of current suggests.
229     *
230     * @return int Size of suggests
231     */
232    public function countSuggests()
233    {
234        return \sizeof($this->getSuggests());
235    }
236
237    /**
238     * Returns the current object of the set.
239     *
240     * @return \Elastica\Result Set object
241     */
242    public function current()
243    {
244        return $this->_results[$this->key()];
245    }
246
247    /**
248     * Sets pointer (current) to the next item of the set.
249     */
250    public function next()
251    {
252        ++$this->_position;
253    }
254
255    /**
256     * Returns the position of the current entry.
257     *
258     * @return int Current position
259     */
260    public function key()
261    {
262        return $this->_position;
263    }
264
265    /**
266     * Check if an object exists at the current position.
267     *
268     * @return bool True if object exists
269     */
270    public function valid()
271    {
272        return isset($this->_results[$this->key()]);
273    }
274
275    /**
276     * Resets position to 0, restarts iterator.
277     */
278    public function rewind()
279    {
280        $this->_position = 0;
281    }
282
283    /**
284     * Whether a offset exists.
285     *
286     * @see http://php.net/manual/en/arrayaccess.offsetexists.php
287     *
288     * @param int $offset
289     *
290     * @return bool true on success or false on failure
291     */
292    public function offsetExists($offset)
293    {
294        return isset($this->_results[$offset]);
295    }
296
297    /**
298     * Offset to retrieve.
299     *
300     * @see http://php.net/manual/en/arrayaccess.offsetget.php
301     *
302     * @param int $offset
303     *
304     * @throws Exception\InvalidException If offset doesn't exist
305     *
306     * @return Result
307     */
308    public function offsetGet($offset)
309    {
310        if ($this->offsetExists($offset)) {
311            return $this->_results[$offset];
312        }
313
314        throw new InvalidException('Offset does not exist.');
315    }
316
317    /**
318     * Offset to set.
319     *
320     * @see http://php.net/manual/en/arrayaccess.offsetset.php
321     *
322     * @param int    $offset
323     * @param Result $value
324     *
325     * @throws Exception\InvalidException
326     */
327    public function offsetSet($offset, $value)
328    {
329        if (!($value instanceof Result)) {
330            throw new InvalidException('ResultSet is a collection of Result only.');
331        }
332
333        if (!isset($this->_results[$offset])) {
334            throw new InvalidException('Offset does not exist.');
335        }
336
337        $this->_results[$offset] = $value;
338    }
339
340    /**
341     * Offset to unset.
342     *
343     * @see http://php.net/manual/en/arrayaccess.offsetunset.php
344     *
345     * @param int $offset
346     */
347    public function offsetUnset($offset)
348    {
349        unset($this->_results[$offset]);
350    }
351}
352