1<?php
2/**
3 * Elasticsearch PHP client
4 *
5 * @link      https://github.com/elastic/elasticsearch-php/
6 * @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co)
7 * @license   http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
8 * @license   https://www.gnu.org/licenses/lgpl-2.1.html GNU Lesser General Public License, Version 2.1
9 *
10 * Licensed to Elasticsearch B.V under one or more agreements.
11 * Elasticsearch B.V licenses this file to you under the Apache 2.0 License or
12 * the GNU Lesser General Public License, Version 2.1, at your option.
13 * See the LICENSE file in the project root for more information.
14 */
15
16
17declare(strict_types = 1);
18
19namespace Elasticsearch\Helper\Iterators;
20
21use Elasticsearch\Client;
22use Iterator;
23
24class SearchResponseIterator implements Iterator
25{
26
27    /**
28     * @var Client
29     */
30    private $client;
31
32    /**
33     * @var array
34     */
35    private $params;
36
37    /**
38     * @var int
39     */
40    private $current_key = 0;
41
42    /**
43     * @var array
44     */
45    private $current_scrolled_response;
46
47    /**
48     * @var string
49     */
50    private $scroll_id;
51
52    /**
53     * @var string duration
54     */
55    private $scroll_ttl;
56
57    /**
58     * Constructor
59     *
60     * @param Client $client
61     * @param array  $search_params Associative array of parameters
62     * @see   Client::search()
63     */
64    public function __construct(Client $client, array $search_params)
65    {
66        $this->client = $client;
67        $this->params = $search_params;
68
69        if (isset($search_params['scroll'])) {
70            $this->scroll_ttl = $search_params['scroll'];
71        }
72    }
73
74    /**
75     * Destructor
76     */
77    public function __destruct()
78    {
79        $this->clearScroll();
80    }
81
82    /**
83     * Sets the time to live duration of a scroll window
84     *
85     * @param  string $time_to_live
86     * @return $this
87     */
88    public function setScrollTimeout(string $time_to_live): SearchResponseIterator
89    {
90        $this->scroll_ttl = $time_to_live;
91        return $this;
92    }
93
94    /**
95     * Clears the current scroll window if there is a scroll_id stored
96     *
97     * @return void
98     */
99    private function clearScroll(): void
100    {
101        if (!empty($this->scroll_id)) {
102            $this->client->clearScroll(
103                [
104                    'body' => [
105                        'scroll_id' => $this->scroll_id
106                    ],
107                    'client' => [
108                        'ignore' => 404
109                    ]
110                ]
111            );
112            $this->scroll_id = null;
113        }
114    }
115
116    /**
117     * Rewinds the iterator by performing the initial search.
118     *
119     * @return void
120     * @see    Iterator::rewind()
121     */
122    public function rewind(): void
123    {
124        $this->clearScroll();
125        $this->current_key = 0;
126        $this->current_scrolled_response = $this->client->search($this->params);
127        $this->scroll_id = $this->current_scrolled_response['_scroll_id'];
128    }
129
130    /**
131     * Fetches every "page" after the first one using the lastest "scroll_id"
132     *
133     * @return void
134     * @see    Iterator::next()
135     */
136    public function next(): void
137    {
138        $this->current_scrolled_response = $this->client->scroll(
139            [
140                'body' => [
141                    'scroll_id' => $this->scroll_id,
142                    'scroll'    => $this->scroll_ttl
143                ]
144            ]
145        );
146        $this->scroll_id = $this->current_scrolled_response['_scroll_id'];
147        $this->current_key++;
148    }
149
150    /**
151     * Returns a boolean value indicating if the current page is valid or not
152     *
153     * @return bool
154     * @see    Iterator::valid()
155     */
156    public function valid(): bool
157    {
158        return isset($this->current_scrolled_response['hits']['hits'][0]);
159    }
160
161    /**
162     * Returns the current "page"
163     *
164     * @return array
165     * @see    Iterator::current()
166     */
167    public function current(): array
168    {
169        return $this->current_scrolled_response;
170    }
171
172    /**
173     * Returns the current "page number" of the current "page"
174     *
175     * @return int
176     * @see    Iterator::key()
177     */
178    public function key(): int
179    {
180        return $this->current_key;
181    }
182}
183