-
Notifications
You must be signed in to change notification settings - Fork 964
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'hkulekci-iterator-helpers'
- Loading branch information
Showing
4 changed files
with
707 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
<?php | ||
/** | ||
* Elasticsearch PHP Client | ||
* | ||
* @link https://github.com/elastic/elasticsearch-php | ||
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co) | ||
* @license https://opensource.org/licenses/MIT MIT License | ||
* | ||
* Licensed to Elasticsearch B.V under one or more agreements. | ||
* Elasticsearch B.V licenses this file to you under the MIT License. | ||
* See the LICENSE file in the project root for more information. | ||
*/ | ||
declare(strict_types = 1); | ||
|
||
namespace Elastic\Elasticsearch\Helper\Iterators; | ||
|
||
use Countable; | ||
use Elastic\Elasticsearch\Exception\ClientResponseException; | ||
use Elastic\Elasticsearch\Exception\ServerResponseException; | ||
use Iterator; | ||
|
||
class SearchHitIterator implements Iterator, Countable | ||
{ | ||
|
||
/** | ||
* @var SearchResponseIterator | ||
*/ | ||
private SearchResponseIterator $searchResponses; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected int $currentKey; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected int $currentHitIndex; | ||
|
||
/** | ||
* @var array|null | ||
*/ | ||
protected ?array $currentHitData; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
protected int $count = 0; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param SearchResponseIterator $searchResponses | ||
*/ | ||
public function __construct(SearchResponseIterator $searchResponses) | ||
{ | ||
$this->searchResponses = $searchResponses; | ||
} | ||
|
||
/** | ||
* Rewinds the internal SearchResponseIterator and itself | ||
* | ||
* @return void | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
* @see Iterator::rewind() | ||
*/ | ||
public function rewind(): void | ||
{ | ||
$this->currentKey = 0; | ||
$this->searchResponses->rewind(); | ||
|
||
// The first page may be empty. In that case, the next page is fetched. | ||
$currentPage = $this->searchResponses->current(); | ||
if ($this->searchResponses->valid() && empty($currentPage['hits']['hits'])) { | ||
$this->searchResponses->next(); | ||
} | ||
|
||
$this->count = 0; | ||
if (isset($currentPage['hits']['total']['value'], $currentPage['hits']['total'])) { | ||
$this->count = $currentPage['hits']['total']['value'] ?? $currentPage['hits']['total']; | ||
} | ||
|
||
$this->readPageData(); | ||
} | ||
|
||
/** | ||
* Advances pointer of the current hit to the next one in the current page. If there | ||
* isn't a next hit in the current page, then it advances the current page and moves the | ||
* pointer to the first hit in the page. | ||
* | ||
* @return void | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
* @see Iterator::next() | ||
*/ | ||
public function next(): void | ||
{ | ||
$this->currentKey++; | ||
$this->currentHitIndex++; | ||
$currentPage = $this->searchResponses->current(); | ||
if (isset($currentPage['hits']['hits'][$this->currentHitIndex])) { | ||
$this->currentHitData = $currentPage['hits']['hits'][$this->currentHitIndex]; | ||
} else { | ||
$this->searchResponses->next(); | ||
$this->readPageData(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns a boolean indicating whether or not the current pointer has valid data | ||
* | ||
* @return bool | ||
* @see Iterator::valid() | ||
*/ | ||
public function valid(): bool | ||
{ | ||
return is_array($this->currentHitData); | ||
} | ||
|
||
/** | ||
* Returns the current hit | ||
* | ||
* @return array | ||
* @see Iterator::current() | ||
*/ | ||
public function current(): array | ||
{ | ||
return $this->currentHitData; | ||
} | ||
|
||
/** | ||
* Returns the current hit index. The hit index spans all pages. | ||
* | ||
* @return int | ||
* @see Iterator::key() | ||
*/ | ||
public function key(): int | ||
{ | ||
return $this->currentKey; | ||
} | ||
|
||
/** | ||
* Advances the internal SearchResponseIterator and resets the currentHitIndex to 0 | ||
* | ||
* @internal | ||
*/ | ||
private function readPageData(): void | ||
{ | ||
if ($this->searchResponses->valid()) { | ||
$currentPage = $this->searchResponses->current(); | ||
$this->currentHitIndex = 0; | ||
$this->currentHitData = $currentPage['hits']['hits'][$this->currentHitIndex]; | ||
} else { | ||
$this->currentHitData = null; | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function count(): int | ||
{ | ||
return $this->count; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
<?php | ||
/** | ||
* Elasticsearch PHP Client | ||
* | ||
* @link https://github.com/elastic/elasticsearch-php | ||
* @copyright Copyright (c) Elasticsearch B.V (https://www.elastic.co) | ||
* @license https://opensource.org/licenses/MIT MIT License | ||
* | ||
* Licensed to Elasticsearch B.V under one or more agreements. | ||
* Elasticsearch B.V licenses this file to you under the MIT License. | ||
* See the LICENSE file in the project root for more information. | ||
*/ | ||
declare(strict_types = 1); | ||
|
||
namespace Elastic\Elasticsearch\Helper\Iterators; | ||
|
||
use Elastic\Elasticsearch\ClientInterface; | ||
use Elastic\Elasticsearch\Exception\ClientResponseException; | ||
use Elastic\Elasticsearch\Exception\ServerResponseException; | ||
use Iterator; | ||
|
||
class SearchResponseIterator implements Iterator | ||
{ | ||
|
||
/** | ||
* @var ClientInterface | ||
*/ | ||
private ClientInterface $client; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private array $params; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
private int $currentKey = 0; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private array $currentScrolledResponse; | ||
|
||
/** | ||
* @var string|null | ||
*/ | ||
private ?string $scrollId; | ||
|
||
/** | ||
* @var string duration | ||
*/ | ||
private $scrollTtl; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param ClientInterface $client | ||
* @param array $searchParams Associative array of parameters | ||
* @see ClientInterface::search() | ||
*/ | ||
public function __construct(ClientInterface $client, array $searchParams) | ||
{ | ||
$this->client = $client; | ||
$this->params = $searchParams; | ||
|
||
if (isset($searchParams['scroll'])) { | ||
$this->scrollTtl = $searchParams['scroll']; | ||
} | ||
} | ||
|
||
/** | ||
* Destructor | ||
* | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
*/ | ||
public function __destruct() | ||
{ | ||
$this->clearScroll(); | ||
} | ||
|
||
/** | ||
* Sets the time to live duration of a scroll window | ||
* | ||
* @param string $timeToLive | ||
* @return $this | ||
*/ | ||
public function setScrollTimeout(string $timeToLive): SearchResponseIterator | ||
{ | ||
$this->scrollTtl = $timeToLive; | ||
return $this; | ||
} | ||
|
||
/** | ||
* Clears the current scroll window if there is a scroll_id stored | ||
* | ||
* @return void | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
*/ | ||
private function clearScroll(): void | ||
{ | ||
if (!empty($this->scrollId)) { | ||
/* @phpstan-ignore-next-line */ | ||
$this->client->clearScroll( | ||
[ | ||
'body' => [ | ||
'scroll_id' => $this->scrollId | ||
], | ||
'client' => [ | ||
'ignore' => 404 | ||
] | ||
] | ||
); | ||
$this->scrollId = null; | ||
} | ||
} | ||
|
||
/** | ||
* Rewinds the iterator by performing the initial search. | ||
* | ||
* @return void | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
* @see Iterator::rewind() | ||
*/ | ||
public function rewind(): void | ||
{ | ||
$this->clearScroll(); | ||
$this->currentKey = 0; | ||
/* @phpstan-ignore-next-line */ | ||
$this->currentScrolledResponse = $this->client->search($this->params)->asArray(); | ||
$this->scrollId = $this->currentScrolledResponse['_scroll_id']; | ||
} | ||
|
||
/** | ||
* Fetches every "page" after the first one using the lastest "scroll_id" | ||
* | ||
* @return void | ||
* @throws ClientResponseException | ||
* @throws ServerResponseException | ||
* @see Iterator::next() | ||
*/ | ||
public function next(): void | ||
{ | ||
/* @phpstan-ignore-next-line */ | ||
$this->currentScrolledResponse = $this->client->scroll( | ||
[ | ||
'body' => [ | ||
'scroll_id' => $this->scrollId, | ||
'scroll' => $this->scrollTtl | ||
] | ||
] | ||
)->asArray(); | ||
$this->scrollId = $this->currentScrolledResponse['_scroll_id']; | ||
$this->currentKey++; | ||
} | ||
|
||
/** | ||
* Returns a boolean value indicating if the current page is valid or not | ||
* | ||
* @return bool | ||
* @see Iterator::valid() | ||
*/ | ||
public function valid(): bool | ||
{ | ||
return isset($this->currentScrolledResponse['hits']['hits'][0]); | ||
} | ||
|
||
/** | ||
* Returns the current "page" | ||
* | ||
* @return array | ||
* @see Iterator::current() | ||
*/ | ||
public function current(): array | ||
{ | ||
return $this->currentScrolledResponse; | ||
} | ||
|
||
/** | ||
* Returns the current "page number" of the current "page" | ||
* | ||
* @return int | ||
* @see Iterator::key() | ||
*/ | ||
public function key(): int | ||
{ | ||
return $this->currentKey; | ||
} | ||
} |
Oops, something went wrong.