Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat(search): multi index search started
A ScopedEntryPoint has been added to support multi indexes search.

The configuration has been improved to add a new key 'scoped_indexes' that allow to define groups of indexes
  • Loading branch information
Guikingone committed Jan 6, 2021
commit 20704e43eb7666099b7e1ba4a51a50deaab08fc6
5 changes: 5 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->arrayNode('scoped_indexes')
->info('A list of indexes to search in when trying to find documents')
->useAttributeAsKey('name')
->variablePrototype()->end()
->end()
->end()
->end()
;
Expand Down
60 changes: 60 additions & 0 deletions src/Search/ScopedSearchEntryPoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace MeiliSearchBundle\Search;

use InvalidArgumentException;
use RuntimeException;
use function array_key_exists;
use function count;

/**
* @author Guillaume Loulier <[email protected]>
*/
final class ScopedSearchEntryPoint implements SearchEntryPointInterface
{
/**
* @var array<string, <int, string>>
*/
private $scopedIndexes;

/**
* @var SearchEntryPointInterface
*/
private $searchEntryPoint;

/**
* @param array<string, <int, string>> $scopedIndexes
* @param SearchEntryPointInterface $searchEntryPoint
*/
public function __construct(
array $scopedIndexes,
SearchEntryPointInterface $searchEntryPoint
) {
$this->scopedIndexes = $scopedIndexes;
$this->searchEntryPoint = $searchEntryPoint;
}

/**
* {@inheritdoc}
*/
public function search(string $index, string $query, array $options = []): SearchResultInterface
{
if (!array_key_exists($index, $this->scopedIndexes)) {
throw new InvalidArgumentException('The desired index is not available');
}

foreach ($this->scopedIndexes[$index] as $usedIndex) {
$result = $this->searchEntryPoint->search($usedIndex, $query, $options);

if (0 === count($result->getHits())) {
continue;
}

return $result;
}

throw new RuntimeException('No result can be found');
}
}
23 changes: 23 additions & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,27 @@ public function testConfigurationCanDefineSynonyms(): void
static::assertContainsEquals('id', $configuration['indexes']['foo']['synonyms']['bar']);
static::assertContainsEquals('title', $configuration['indexes']['foo']['synonyms']['bar']);
}

public function testScopedIndexesCanBeCreated(): void
{
$configuration = (new Processor())->processConfiguration(new Configuration(), [
'meili_search' => [
'apiKey' => 'test',
'indexes' => [],
'scoped_indexes' => [
'foo' => ['bar', 'random'],
'bar' => ['foo', 'random'],
],
],
]);

self::assertArrayHasKey('scoped_indexes', $configuration);
self::assertCount(2, $configuration['scoped_indexes']);
self::assertArrayHasKey('foo', $configuration['scoped_indexes']);
self::assertArrayHasKey('bar', $configuration['scoped_indexes']);
self::assertContains('bar', $configuration['scoped_indexes']['foo']);
self::assertContains('random', $configuration['scoped_indexes']['foo']);
self::assertContains('foo', $configuration['scoped_indexes']['bar']);
self::assertContains('random', $configuration['scoped_indexes']['bar']);
}
}
84 changes: 84 additions & 0 deletions tests/Search/ScopedSearchEntryPointTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Tests\MeiliSearchBundle\Search;

use InvalidArgumentException;
use MeiliSearchBundle\Search\ScopedSearchEntryPoint;
use MeiliSearchBundle\Search\SearchEntryPointInterface;
use MeiliSearchBundle\Search\SearchResultInterface;
use PHPUnit\Framework\TestCase;
use RuntimeException;

/**
* @author Guillaume Loulier <[email protected]>
*/
final class ScopedSearchEntryPointTest extends TestCase
{
public function testEntryPointCannotSearchOnInvalidIndex(): void
{
$entryPoint = $this->createMock(SearchEntryPointInterface::class);

$scopedEntryPoint = new ScopedSearchEntryPoint([], $entryPoint);

self::expectException(InvalidArgumentException::class);
self::expectErrorMessage('The desired index is not available');
self::expectExceptionCode(0);
$scopedEntryPoint->search('foo', 'bar');
}

public function testEntryPointCannotBeSearchedWithoutHits(): void
{
$result = $this->createMock(SearchResultInterface::class);
$result->expects(self::once())->method('getHits')->willReturn([]);

$secondResult = $this->createMock(SearchResultInterface::class);
$secondResult->expects(self::once())->method('getHits')->willReturn([]);

$entryPoint = $this->createMock(SearchEntryPointInterface::class);
$entryPoint->expects(self::exactly(2))->method('search')->withConsecutive([
self::equalTo('bar'), self::equalTo('bar'),
], [
self::equalTo('random'), self::equalTo('bar'),
])->willReturnOnConsecutiveCalls($result, $secondResult);

$scopedEntryPoint = new ScopedSearchEntryPoint([
'foo' => ['bar', 'random'],
'bar' => ['foo', 'random'],
], $entryPoint);

self::expectException(RuntimeException::class);
self::expectExceptionMessage('No result can be found');
self::expectExceptionCode(0);
$scopedEntryPoint->search('foo', 'bar');
}

public function testEntryPointCanReturnResult(): void
{
$result = $this->createMock(SearchResultInterface::class);
$result->expects(self::exactly(2))->method('getHits')->willReturn([
'id' => 'foo',
'title' => 'bar',
]);

$secondResult = $this->createMock(SearchResultInterface::class);
$secondResult->expects(self::never())->method('getHits');

$entryPoint = $this->createMock(SearchEntryPointInterface::class);
$entryPoint->expects(self::exactly(2))->method('search')->withConsecutive([
self::equalTo('bar'), self::equalTo('bar'),
], [
self::equalTo('random'), self::equalTo('bar'),
])->willReturnOnConsecutiveCalls($result, $secondResult);

$scopedEntryPoint = new ScopedSearchEntryPoint([
'foo' => ['bar', 'random'],
'bar' => ['foo', 'random'],
], $entryPoint);

$searchResult = $scopedEntryPoint->search('foo', 'bar');

self::assertArrayHasKey('id', $secondResult->getHits());
}
}