Skip to content
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
52 changes: 52 additions & 0 deletions Adapter/SolariumResultPromisePagerfantaAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Markup\NeedleBundle\Adapter;

use GuzzleHttp\Promise\PromiseInterface;
use Pagerfanta\Adapter\AdapterInterface;
use Solarium\QueryType\Select\Result\Result;

class SolariumResultPromisePagerfantaAdapter implements AdapterInterface
{
/**
* @var PromiseInterface
*/
private $resultPromise;

public function __construct(PromiseInterface $resultPromise)
{
$this->resultPromise = $resultPromise;
}

/**
* Returns the number of results.
*
* @return integer The number of results.
*/
public function getNbResults()
{
return $this->getResult()->getNumFound();
}

/**
* Returns an slice of the results.
*
* @param integer $offset The offset.
* @param integer $length The length.
*
* @return array|\Traversable The slice.
*/
public function getSlice($offset, $length)
{
// ignore offset/length as that should have been predetermined
return $this->getResult();
}

/**
* @return Result
*/
private function getResult()
{
return $this->resultPromise->wait();
}
}
13 changes: 13 additions & 0 deletions Query/FallbackAsyncQueryTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Markup\NeedleBundle\Query;

use function GuzzleHttp\Promise\promise_for;

trait FallbackAsyncQueryTrait
{
public function getResultAsync()
{
return promise_for($this->getResult());
}
}
8 changes: 8 additions & 0 deletions Query/ResolvedSelectQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ public function getResult()
return $this->getSelectQuery()->getResult();
}

/**
* {@inheritdoc}
*/
public function getResultAsync()
{
return $this->getSelectQuery()->getResultAsync();
}

/**
* {@inheritdoc}
*/
Expand Down
8 changes: 8 additions & 0 deletions Query/SelectQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Markup\NeedleBundle\Query;

use GuzzleHttp\Promise\PromiseInterface;
use Markup\NeedleBundle\Filter\FilterQueryInterface;
use Markup\NeedleBundle\Service\SearchServiceInterface as SearchService;
use Markup\NeedleBundle\Spellcheck\SpellcheckInterface;
Expand Down Expand Up @@ -73,6 +74,13 @@ public function getFacetNamesToExclude();
**/
public function getResult();

/**
* Gets the result of the query as a promise.
*
* @return PromiseInterface
*/
public function getResultAsync();

/**
* @param SearchService $service
**/
Expand Down
17 changes: 17 additions & 0 deletions Service/AsyncSearchServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Markup\NeedleBundle\Service;

use GuzzleHttp\Promise\PromiseInterface;
use Markup\NeedleBundle\Query\SelectQueryInterface;

interface AsyncSearchServiceInterface extends SearchServiceInterface
{
/**
* Gets a promise which, when being resolved, executes a select query on a service and makes a result available.
*
* @param SelectQueryInterface
* @return PromiseInterface
**/
public function executeQueryAsync(SelectQueryInterface $query);
}
133 changes: 83 additions & 50 deletions Service/SolrSearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
namespace Markup\NeedleBundle\Service;

use Doctrine\Common\Collections\ArrayCollection;
use Markup\NeedleBundle\Adapter\SolariumGroupedQueryPagerfantaAdapter;
use function GuzzleHttp\Promise\coroutine;
use function GuzzleHttp\Promise\promise_for;
use GuzzleHttp\Promise\PromiseInterface;
use Markup\NeedleBundle\Adapter\GroupedResultAdapter;
use Markup\NeedleBundle\Adapter\SolariumResultPromisePagerfantaAdapter;
use Markup\NeedleBundle\Builder\SolariumSelectQueryBuilder;
use Markup\NeedleBundle\Context\SearchContextInterface;
use Markup\NeedleBundle\Query\ResolvedSelectQuery;
Expand All @@ -13,8 +17,8 @@
use Markup\NeedleBundle\Result\SolariumDebugOutputStrategy;
use Markup\NeedleBundle\Result\SolariumFacetSetsStrategy;
use Markup\NeedleBundle\Result\SolariumSpellcheckResultStrategy;
use Pagerfanta\Adapter\SolariumAdapter;
use Pagerfanta\Pagerfanta;
use Shieldo\SolariumAsyncPlugin;
use Solarium\Client as SolariumClient;
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;

Expand All @@ -24,7 +28,7 @@
* Composes a SelectQuery & SearchContext (into a ResolvedQuery), converts it into a solr query and
* executes the query (using Solarium) and then returns a result (as a PagerfantaResultAdapter)
*/
class SolrSearchService implements SearchServiceInterface
class SolrSearchService implements AsyncSearchServiceInterface
{
/**
* Solr needs specification of a very large number for a 'view all' function.
Expand Down Expand Up @@ -85,54 +89,83 @@ public function __construct(
*/
public function executeQuery(SelectQueryInterface $query)
{
$solariumQueryBuilder = $this->getSolariumQueryBuilder();

$query = new ResolvedSelectQuery($query, $this->hasContext() ? $this->getContext() : null);

foreach ($this->decorators as $decorator) {
$query = $decorator->decorate($query);
}

$solariumQuery = $solariumQueryBuilder->buildSolariumQueryFromGeneric($query);

if ($query->getGroupingField()) {
$pagerfantaAdapter = new SolariumGroupedQueryPagerfantaAdapter($this->getSolariumClient(), $solariumQuery);
} else {
$pagerfantaAdapter = new SolariumAdapter($this->getSolariumClient(), $solariumQuery);
}

$pagerfanta = new Pagerfanta($pagerfantaAdapter);
$maxPerPage = $query->getMaxPerPage();
if (null === $maxPerPage && $this->hasContext() && $query->getPageNumber() !== null) {
$maxPerPage = $this->getContext()->getItemsPerPage() ?: null;
}
$pagerfanta->setMaxPerPage($maxPerPage ?: self::INFINITY);
$page = $query->getPageNumber();
if ($page) {
$pagerfanta->setCurrentPage($page, false, true);
}

$result = new PagerfantaResultAdapter($pagerfanta);
$resultClosure = function () use ($pagerfantaAdapter) {
return $pagerfantaAdapter->getResultSet();
};

//set the strategy to fetch facet sets, as these are not handled by pagerfanta
if ($this->hasContext()) {
$result->setFacetSetStrategy(
new SolariumFacetSetsStrategy($resultClosure, $this->getContext(), $query->getRecord())
);
}

//set any spellcheck result
$result->setSpellcheckResultStrategy(new SolariumSpellcheckResultStrategy($resultClosure, $query));

//set strategy for debug information output as this is not available through pagerfanta - only if templating service was available
if (null !== $this->templating) {
$result->setDebugOutputStrategy(new SolariumDebugOutputStrategy($resultClosure, $this->templating));
}
return $this->executeQueryAsync($query)->wait();
}

return $result;
/**
* Provides a promise for a executing a select query on a service, returning a result.
*
* @param SelectQueryInterface
* @return PromiseInterface
**/
public function executeQueryAsync(SelectQueryInterface $query)
{
return coroutine(
function () use ($query) {
$solariumQueryBuilder = $this->getSolariumQueryBuilder();

$query = new ResolvedSelectQuery($query, $this->hasContext() ? $this->getContext() : null);

foreach ($this->decorators as $decorator) {
$query = $decorator->decorate($query);
}
$solariumQuery = $solariumQueryBuilder->buildSolariumQueryFromGeneric($query);

//apply offset/limit
$maxPerPage = $query->getMaxPerPage();
if (null === $maxPerPage && $this->hasContext() && $query->getPageNumber() !== null) {
$maxPerPage = $this->getContext()->getItemsPerPage() ?: null;
}
$solariumQuery->setRows($maxPerPage ?: self::INFINITY);


$page = $query->getPageNumber();
if ($page && $maxPerPage) {
$solariumQuery->setStart($maxPerPage * ($page-1));
}

$pluginIndex = 'async';
/** @var SolariumAsyncPlugin $asyncPlugin */
$asyncPlugin = $this->solarium
->registerPlugin($pluginIndex, new SolariumAsyncPlugin())
->getPlugin($pluginIndex);

$solariumResult = $this->solarium->createResult(
$solariumQuery,
(yield promise_for($asyncPlugin->queryAsync($solariumQuery)))
);
if ($query->getGroupingField()) {
$solariumResult = new GroupedResultAdapter($solariumResult);
}

$pagerfanta = new Pagerfanta(new SolariumResultPromisePagerfantaAdapter(promise_for($solariumResult)));
$pagerfanta->setCurrentPage($page ?: 1);
$pagerfanta->setMaxPerPage($maxPerPage ?: self::INFINITY);

$result = new PagerfantaResultAdapter($pagerfanta);

$resultClosure = function () use ($solariumResult) {
return $solariumResult;
};

//set the strategy to fetch facet sets, as these are not handled by pagerfanta
if ($this->hasContext()) {
$result->setFacetSetStrategy(
new SolariumFacetSetsStrategy($resultClosure, $this->getContext(), $query->getRecord())
);
}

//set any spellcheck result
$result->setSpellcheckResultStrategy(new SolariumSpellcheckResultStrategy($resultClosure, $query));

//set strategy for debug information output as this is not available through pagerfanta - only if templating service was available
if (null !== $this->templating) {
$result->setDebugOutputStrategy(new SolariumDebugOutputStrategy($resultClosure, $this->templating));
}

yield $result;
}
);
}

/**
Expand Down
49 changes: 49 additions & 0 deletions Tests/Adapter/SolariumResultPromisePagerfantaAdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Markup\NeedleBundle\Tests\Adapter;

use function GuzzleHttp\Promise\promise_for;
use Markup\NeedleBundle\Adapter\SolariumResultPromisePagerfantaAdapter;
use Markup\NeedleBundle\Result\ResultInterface;
use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Pagerfanta\Adapter\AdapterInterface;
use Solarium\QueryType\Select\Result\Result;

class SolariumResultPromisePagerfantaAdapterTest extends MockeryTestCase
{
/**
* @var Result|m\MockInterface
*/
private $result;

/**
* @var SolariumResultPromisePagerfantaAdapter
*/
private $adapter;

protected function setUp()
{
$this->result = m::mock(ResultInterface::class);
$this->adapter = new SolariumResultPromisePagerfantaAdapter(promise_for($this->result));
}

public function testIsPagerfantaAdapter()
{
$this->assertInstanceOf(AdapterInterface::class, $this->adapter);
}

public function testGetSliceReturnsResultRegardlessOfInputs()
{
$this->assertSame($this->result, $this->adapter->getSlice(21, 20));
}

public function testGetNbResultsUsesNumFound()
{
$count = 42;
$this->result
->shouldReceive('getNumFound')
->andReturn($count);
$this->assertEquals($count, $this->adapter->getNbResults());
}
}
5 changes: 4 additions & 1 deletion Tests/Query/RecordableSelectQueryInterfaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Markup\NeedleBundle\Tests\Query;

use Markup\NeedleBundle\Query\RecordableSelectQueryInterface;

/**
* A test for a recordable select query interface.
*/
Expand All @@ -20,6 +22,7 @@ public function testHasCorrectPublicMethods()
'hasSortCollection',
'getFacetNamesToExclude',
'getResult',
'getResultAsync',
'setSearchService',
'record',
'getRecord',
Expand All @@ -32,7 +35,7 @@ public function testHasCorrectPublicMethods()
'getGroupingField',
'getGroupingSortCollection'
];
$query = new \ReflectionClass('Markup\NeedleBundle\Query\RecordableSelectQueryInterface');
$query = new \ReflectionClass(RecordableSelectQueryInterface::class);
$actual_public_methods = [];
foreach ($query->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$actual_public_methods[] = $method->name;
Expand Down
5 changes: 4 additions & 1 deletion Tests/Query/ResolvedSelectQueryInterfaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Markup\NeedleBundle\Tests\Query;

use Markup\NeedleBundle\Query\ResolvedSelectQueryInterface;

/**
* A test for a select query interface.
*/
Expand All @@ -20,6 +22,7 @@ public function testHasCorrectPublicMethods()
'hasSortCollection',
'getFacetNamesToExclude',
'getResult',
'getResultAsync',
'setSearchService',
'getFilterQueryWithKey',
'doesValueExistInFilterQueries',
Expand All @@ -37,7 +40,7 @@ public function testHasCorrectPublicMethods()
'getGroupingField',
'getGroupingSortCollection'
];
$query = new \ReflectionClass('Markup\NeedleBundle\Query\ResolvedSelectQueryInterface');
$query = new \ReflectionClass(ResolvedSelectQueryInterface::class);
$actual_public_methods = [];
foreach ($query->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$actual_public_methods[] = $method->name;
Expand Down
Loading