Skip to content

Commit

Permalink
IBX-6649: Added support for spell checking
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwojs committed Sep 28, 2023
1 parent fe38408 commit 1d83968
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 6 deletions.
7 changes: 3 additions & 4 deletions src/contracts/Repository/Values/Content/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace Ibexa\Contracts\Core\Repository\Values\Content;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Spellcheck;
use Ibexa\Contracts\Core\Repository\Values\ValueObject;

/**
Expand Down Expand Up @@ -91,11 +92,9 @@ class Query extends ValueObject
public $limit = 25;

/**
* If true spellcheck suggestions are returned.
*
* @var bool
* Spellcheck suggestions are returned.
*/
public $spellcheck;
public ?Spellcheck $spellcheck = null;

/**
* If true, search engine should perform count even if that means extra lookup.
Expand Down
26 changes: 26 additions & 0 deletions src/contracts/Repository/Values/Content/Query/Spellcheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\Values\Content\Query;

use Ibexa\Contracts\Core\Repository\Values\ValueObject;

final class Spellcheck extends ValueObject
{
private string $query;

public function __construct(string $query)
{
$this->query = $query;
}

public function getQuery(): string
{
return $this->query;
}
}
14 changes: 13 additions & 1 deletion src/contracts/Repository/Values/Content/Search/SearchResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ class SearchResult extends ValueObject implements IteratorAggregate, Aggregation
* criterions the wrong spelled value is replaced by a corrected one (TBD).
*
* @var \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion
*
* @deprecated since Ibexa 4.6.0, to be removed in Ibexa 5.0.0.
*/
public $spellSuggestion;

public ?SpellcheckResult $spellcheck = null;

/**
* The duration of the search processing in ms.
*
Expand All @@ -69,7 +73,10 @@ class SearchResult extends ValueObject implements IteratorAggregate, Aggregation
public $maxScore;

/**
* The total number of searchHits.
* The total number of searchHits. public function getSpellSuggestion(): ?SpellcheckResult.
{
return $this->spellSuggestion;
}
*
* `null` if Query->performCount was set to false and search engine avoids search lookup.
*
Expand All @@ -86,6 +93,11 @@ public function __construct(array $properties = [])
parent::__construct($properties);
}

public function getSpellcheck(): ?SpellcheckResult
{
return $this->spellcheck;
}

public function getAggregations(): ?AggregationResultCollection
{
return $this->aggregations;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\Values\Content\Search;

final class SpellcheckResult
{
/**
* Query with applied corrections.
*/
private ?string $query;

/**
* Flag indicating that corrections has been applied to input query.
*/
private bool $incorrect;

public function __construct(?string $query, bool $incorrect = true)
{
$this->query = $query;
$this->incorrect = $incorrect;
}

public function getQuery(): ?string
{
return $this->query;
}

public function isIncorrect(): bool
{
return $this->incorrect;
}
}
28 changes: 27 additions & 1 deletion src/lib/Pagination/Pagerfanta/AbstractSearchResultAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\AggregationResultCollection;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SpellcheckResult;
use Pagerfanta\Adapter\AdapterInterface;

abstract class AbstractSearchResultAdapter implements AdapterInterface, SearchResultAdapter
Expand Down Expand Up @@ -40,6 +41,8 @@ abstract class AbstractSearchResultAdapter implements AdapterInterface, SearchRe
/** @var int|null */
private $totalCount;

private ?SpellcheckResult $spellcheck = null;

public function __construct(Query $query, SearchService $searchService, array $languageFilter = [])
{
$this->query = $query;
Expand All @@ -60,9 +63,10 @@ public function getNbResults()

$countQuery = clone $this->query;
$countQuery->limit = 0;
// Skip facets/aggregations computing
// Skip facets/aggregations & spellcheck computing
$countQuery->facetBuilders = [];
$countQuery->aggregations = [];
$countQuery->spellcheck = null;

$searchResults = $this->executeQuery(
$this->searchService,
Expand Down Expand Up @@ -98,6 +102,7 @@ public function getSlice($offset, $length)
$this->time = $searchResult->time;
$this->timedOut = $searchResult->timedOut;
$this->maxScore = $searchResult->maxScore;
$this->spellcheck = $searchResult->getSpellcheck();

// Set count for further use if returned by search engine despite !performCount (Solr, ES)
if (!isset($this->totalCount) && isset($searchResult->totalCount)) {
Expand All @@ -113,6 +118,7 @@ public function getAggregations(): AggregationResultCollection
$aggregationQuery = clone $this->query;
$aggregationQuery->offset = 0;
$aggregationQuery->limit = 0;
$aggregationQuery->spellcheck = null;

$searchResults = $this->executeQuery(
$this->searchService,
Expand All @@ -126,6 +132,26 @@ public function getAggregations(): AggregationResultCollection
return $this->aggregations;
}

public function getSpellcheck(): ?SpellcheckResult
{
if ($this->spellcheck === null) {
$spellcheckQuery = clone $this->query;
$spellcheckQuery->offset = 0;
$spellcheckQuery->limit = 0;
$spellcheckQuery->aggregations = [];

$searchResults = $this->executeQuery(
$this->searchService,
$spellcheckQuery,
$this->languageFilter
);

$this->spellcheck = $searchResults->spellcheck;
}

return $this->spellcheck;
}

public function getTime(): ?float
{
return $this->time;
Expand Down
39 changes: 39 additions & 0 deletions tests/integration/Core/Repository/SearchServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use function count;
use Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException;
use Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException;
use Ibexa\Contracts\Core\Repository\SearchService;
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\Values\Content\Location;
Expand Down Expand Up @@ -4658,6 +4659,44 @@ public function testFulltextLocationTranslationSearch(array $data): void
$this->assertFulltextSearchForTranslations(self::FIND_LOCATION_METHOD, $query);
}

public function testSpellcheckWithIncorrectQuery(): void
{
$searchService = $this->getRepository()->getSearchService();

if (!$searchService->supports(SearchService::CAPABILITY_SPELLCHECK)) {
self::markTestSkipped("Search engine doesn't support spellchecking");
}

$query = new Query();
// Search phrase with typo: "Ibexa Platfomr" instead of "Ibexa Platform":
$query->spellcheck = new Query\Spellcheck('Ibexa Platfomr');

$results = $searchService->findContent($query);

self::assertNotNull($results->spellcheck);
self::assertTrue($results->spellcheck->isIncorrect());
self::assertEquals('Ibexa Platform', $results->spellcheck->getQuery());
}

public function testSpellcheckWithCorrectQuery(): void
{
$searchService = $this->getRepository()->getSearchService();

if (!$searchService->supports(SearchService::CAPABILITY_SPELLCHECK)) {
self::markTestSkipped("Search engine doesn't support spellchecking");
}

$query = new Query();
// Search phrase without typo
$query->spellcheck = new Query\Spellcheck('Ibexa Platform');

$results = $searchService->findContent($query);

self::assertNotNull($results->spellcheck);
self::assertFalse($results->spellcheck->isIncorrect());
self::assertEquals('Ibexa Platform', $results->spellcheck->getQuery());
}

/**
* Assert that query result matches the given fixture.
*
Expand Down

0 comments on commit 1d83968

Please sign in to comment.