Skip to content
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

Add scroll pagination in repository #1812

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Finder/PaginatedFinderInterface.php
Expand Up @@ -55,4 +55,12 @@ public function createHybridPaginatorAdapter($query);
* @return PaginatorAdapterInterface
*/
public function createRawPaginatorAdapter($query, $options = []);

/**
* @param mixed $query
* @param array $options
*
* @return PaginatorAdapterInterface
*/
public function createScrollPaginatorAdapter($query, $options = []);
}
22 changes: 22 additions & 0 deletions src/Finder/TransformedFinder.php
Expand Up @@ -17,6 +17,7 @@
use FOS\ElasticaBundle\Paginator\HybridPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\RawPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\TransformedPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\TransformedScrollPaginatorAdapter;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Pagerfanta\Pagerfanta;

Expand Down Expand Up @@ -80,6 +81,17 @@ public function findPaginated($query, $options = [])
return new Pagerfanta(new FantaPaginatorAdapter($paginatorAdapter));
}

/**
* {@inheritdoc}
*/
public function findScrollPaginated($query, $options = [])
{
$query = Query::create($query);
$paginatorAdapter = $this->createScrollPaginatorAdapter($query, $options);

return new Pagerfanta(new FantaPaginatorAdapter($paginatorAdapter));
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -110,6 +122,16 @@ public function createRawPaginatorAdapter($query, $options = [])
return new RawPaginatorAdapter($this->searchable, $query, $options);
}

/**
* {@inheritdoc}
*/
public function createScrollPaginatorAdapter($query, $options = [])
{
$query = Query::create($query);

return new TransformedScrollPaginatorAdapter($this->searchable, $query, $options, $this->transformer);
}

/**
* @param $query
* @param null|int $limit
Expand Down
185 changes: 185 additions & 0 deletions src/Paginator/RawScrollPaginatorAdapter.php
@@ -0,0 +1,185 @@
<?php

declare(strict_types=1);

namespace FOS\ElasticaBundle\Paginator;

use Elastica\Query;
use Elastica\Response;
use Elastica\ResultSet;
use Elastica\SearchableInterface;
use Elastica\Scroll;
use InvalidArgumentException;

class RawScrollPaginatorAdapter implements PaginatorAdapterInterface
{
/**
* @var SearchableInterface the object to search in
*/
private $searchable;

/**
* @var Scroll the scroll instance
*/
private $scroll;

/**
* @var Query the query to search
*/
private $query;

/**
* @var array search options
*/
private $options;

/**
* @var int the number of hits
*/
private $totalHits;

/**
* @var array for the aggregations
*/
private $aggregations;

/**
* @var array for the suggesters
*/
private $suggests;

/**
* @var float
*/
private $maxScore;

/**
* @see PaginatorAdapterInterface::__construct
*
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options = [])
{
$this->searchable = $searchable;
$this->query = $query;
$this->options = $options;
}

/**
* {@inheritdoc}
*/
public function getResults($offset, $itemCountPerPage)
{
return new RawPartialResults($this->getElasticaResults($offset, $itemCountPerPage));
}

/**
* {@inheritdoc}
*/
public function getTotalHits($genuineTotal = false)
{
if (!isset($this->totalHits)) {
$this->totalHits = $this->searchable->count($this->query);
}

return $this->query->hasParam('size') && !$genuineTotal
? min($this->totalHits, (int) $this->query->getParam('size'))
: $this->totalHits;
}

public function getAggregations()
{
if (!isset($this->aggregations)) {
$this->aggregations = $this->searchable->search($this->query)->getAggregations();
}

return $this->aggregations;
}

/**
* {@inheritdoc}
*/
public function getSuggests()
{
if (!isset($this->suggests)) {
$this->suggests = $this->searchable->search($this->query)->getSuggests();
}

return $this->suggests;
}

/**
* @return float
*/
public function getMaxScore()
{
if (!isset($this->maxScore)) {
$this->maxScore = $this->searchable->search($this->query)->getMaxScore();
}

return $this->maxScore;
}

/**
* Returns the Query.
*
* @return Query the search query
*/
public function getQuery()
{
return $this->query;
}

/**
* Returns the paginated results.
*
* @param int $offset
* @param int $itemCountPerPage
*
* @throws \InvalidArgumentException
*
* @return ResultSet
*/
protected function getElasticaResults($offset, $itemCountPerPage)
{
$offset = (int) $offset;
$itemCountPerPage = (int) $itemCountPerPage;
$size = $this->query->hasParam('size')
? (int) $this->query->getParam('size')
: null;

if (null !== $size && $size < $offset + $itemCountPerPage) {
$itemCountPerPage = $size - $offset;
}

if ($itemCountPerPage < 1) {
throw new InvalidArgumentException('$itemCountPerPage must be greater than zero');
}

// first search
if ($this->scroll === null) {
$query = clone $this->query;
$query->setSize($itemCountPerPage);
$expiryTime = isset($this->options['expiryTime']) ? $this->options['expiryTime'] : '1m';
$this->scroll = $this->searchable->createSearch($query)->scroll($expiryTime);
$this->scroll->rewind();
} else {
$this->scroll->next();
}

$resultSet = $this->scroll->current();

if ($resultSet === null) {
return new ResultSet(new Response([]), $this->query, []);
}

$this->totalHits = $resultSet->getTotalHits();
$this->aggregations = $resultSet->getAggregations();
$this->suggests = $resultSet->getSuggests();
$this->maxScore = $resultSet->getMaxScore();

return $resultSet;
}
}
45 changes: 45 additions & 0 deletions src/Paginator/TransformedScrollPaginatorAdapter.php
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the FOSElasticaBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\ElasticaBundle\Paginator;

use Elastica\Query;
use Elastica\SearchableInterface;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;

/**
* Allows pagination of \Elastica\Query.
*/
class TransformedScrollPaginatorAdapter extends RawScrollPaginatorAdapter
{
private $transformer;

/**
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
* @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options, ElasticaToModelTransformerInterface $transformer)
{
parent::__construct($searchable, $query, $options);

$this->transformer = $transformer;
}

/**
* {@inheritdoc}
*/
public function getResults($offset, $length)
{
return new TransformedPartialResults($this->getElasticaResults($offset, $length), $this->transformer);
}
}
21 changes: 21 additions & 0 deletions src/Repository.php
Expand Up @@ -71,6 +71,16 @@ public function findPaginated($query, $options = [])
* @param string $query
* @param array $options
*
* @return \Pagerfanta\Pagerfanta
*/
public function findScrollPaginated($query, $options = [])
{
return $this->finder->findScrollPaginated($query, $options);
}

/**
* @param mixed $query
*
* @return Paginator\PaginatorAdapterInterface
*/
public function createPaginatorAdapter($query, $options = [])
Expand All @@ -87,4 +97,15 @@ public function createHybridPaginatorAdapter($query)
{
return $this->finder->createHybridPaginatorAdapter($query);
}

/**
* @param mixed $query
* @param array $options
*
* @return Paginator\PaginatorAdapterInterface
*/
public function createScrollPaginatorAdapter($query, $options = [])
{
return $this->finder->createScrollPaginatorAdapter($query, $options);
}
}
23 changes: 23 additions & 0 deletions tests/Unit/Finder/TransformedFinderTest.php
Expand Up @@ -17,6 +17,7 @@
use FOS\ElasticaBundle\Finder\TransformedFinder;
use FOS\ElasticaBundle\Paginator\HybridPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\TransformedPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\TransformedScrollPaginatorAdapter;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Pagerfanta\Pagerfanta;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -97,6 +98,28 @@ public function testCreateHybridPaginatorAdapter()
$this->assertInstanceOf(HybridPaginatorAdapter::class, $finder->createHybridPaginatorAdapter(''));
}

public function testFindScrollPaginated()
{
$searchable = $this->createMock(SearchableInterface::class);
$transformer = $this->createMock(ElasticaToModelTransformerInterface::class);

$finder = new TransformedFinder($searchable, $transformer);

$pagerfanta = $finder->findScrollPaginated('');

$this->assertInstanceOf(Pagerfanta::class, $pagerfanta);
}

public function testCreateScrollPaginatorAdapter()
{
$searchable = $this->createMock(SearchableInterface::class);
$transformer = $this->createMock(ElasticaToModelTransformerInterface::class);

$finder = new TransformedFinder($searchable, $transformer);

$this->assertInstanceOf(TransformedScrollPaginatorAdapter::class, $finder->createScrollPaginatorAdapter(''));
}

private function createMockTransformer($transformMethod)
{
$transformer = $this->createMock(ElasticaToModelTransformerInterface::class);
Expand Down