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