1<?php
2
3declare(strict_types = 1);
4
5namespace Elasticsearch\Helper\Iterators;
6
7use Iterator;
8
9/**
10 * Class SearchHitIterator
11 *
12 * @category Elasticsearch
13 * @package  Elasticsearch\Helper\Iterators
14 * @author   Arturo Mejia <arturo.mejia@kreatetechnology.com>
15 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
16 * @link     http://elastic.co
17 * @see      Iterator
18 */
19class SearchHitIterator implements Iterator, \Countable
20{
21
22    /**
23     * @var SearchResponseIterator
24     */
25    private $search_responses;
26
27    /**
28     * @var int
29     */
30    protected $current_key;
31
32    /**
33     * @var int
34     */
35    protected $current_hit_index;
36
37    /**
38     * @var array|null
39     */
40    protected $current_hit_data;
41
42    /**
43     * @var int
44     */
45    protected $count = 0;
46
47    /**
48     * Constructor
49     *
50     * @param SearchResponseIterator $search_responses
51     */
52    public function __construct(SearchResponseIterator $search_responses)
53    {
54        $this->search_responses = $search_responses;
55    }
56
57    /**
58     * Rewinds the internal SearchResponseIterator and itself
59     *
60     * @return void
61     * @see    Iterator::rewind()
62     */
63    public function rewind(): void
64    {
65        $this->current_key = 0;
66        $this->search_responses->rewind();
67
68        // The first page may be empty. In that case, the next page is fetched.
69        $current_page = $this->search_responses->current();
70        if ($this->search_responses->valid() && empty($current_page['hits']['hits'])) {
71            $this->search_responses->next();
72        }
73
74        $this->count = 0;
75        if (isset($current_page['hits']) && isset($current_page['hits']['total'])) {
76            $this->count = $current_page['hits']['total'];
77        }
78
79        $this->readPageData();
80    }
81
82    /**
83     * Advances pointer of the current hit to the next one in the current page. If there
84     * isn't a next hit in the current page, then it advances the current page and moves the
85     * pointer to the first hit in the page.
86     *
87     * @return void
88     * @see    Iterator::next()
89     */
90    public function next(): void
91    {
92        $this->current_key++;
93        $this->current_hit_index++;
94        $current_page = $this->search_responses->current();
95        if (isset($current_page['hits']['hits'][$this->current_hit_index])) {
96            $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index];
97        } else {
98            $this->search_responses->next();
99            $this->readPageData();
100        }
101    }
102
103    /**
104     * Returns a boolean indicating whether or not the current pointer has valid data
105     *
106     * @return bool
107     * @see    Iterator::valid()
108     */
109    public function valid(): bool
110    {
111        return is_array($this->current_hit_data);
112    }
113
114    /**
115     * Returns the current hit
116     *
117     * @return array
118     * @see    Iterator::current()
119     */
120    public function current(): array
121    {
122        return $this->current_hit_data;
123    }
124
125    /**
126     * Returns the current hit index. The hit index spans all pages.
127     *
128     * @return int
129     * @see    Iterator::key()
130     */
131    public function key(): int
132    {
133        return $this->current_key;
134    }
135
136    /**
137     * Advances the internal SearchResponseIterator and resets the current_hit_index to 0
138     *
139     * @internal
140     */
141    private function readPageData(): void
142    {
143        if ($this->search_responses->valid()) {
144            $current_page = $this->search_responses->current();
145            $this->current_hit_index = 0;
146            $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index];
147        } else {
148            $this->current_hit_data = null;
149        }
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    public function count(): int
156    {
157        return $this->count;
158    }
159}
160