Skip to content

SearchHitIterators and SearchResponseIterator helpers revised with new version #1302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/Helper/Iterators/SearchHitIterator.php
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;
}
}
192 changes: 192 additions & 0 deletions src/Helper/Iterators/SearchResponseIterator.php
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;
}
}
Loading