From 36f7d02261ccb78476af881a863a88591fdb02b9 Mon Sep 17 00:00:00 2001 From: Jeroen Date: Sat, 31 Oct 2020 23:24:37 +0100 Subject: [PATCH] add sorting --- docs/index.md | 1 + docs/sorting.md | 23 ++++++++++++++++++ src/Application/BuildCommand.php | 23 ++++++++++++++++++ src/Application/Finder.php | 4 ++++ src/Domain/Syntax/Sort.php | 30 ++++++++++++++++++++++++ src/ExplorerServiceProvider.php | 5 ++++ tests/Unit/BuildCommandTest.php | 40 ++++++++++++++++++++++++++++++++ 7 files changed, 126 insertions(+) create mode 100644 docs/sorting.md create mode 100644 src/Domain/Syntax/Sort.php diff --git a/docs/index.md b/docs/index.md index 2ebb1f0..bcb79af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,6 +26,7 @@ Also do not forget to follow the [installation instructions for Laravel Scout](h - [Quickstart](quickstart.md) - [Mapping properties in Elasticsearch](mapping.md) - [Advanced queries](advanced-queries.md) +- [Sorting search results](sorting.md) [ico-version]: https://img.shields.io/packagist/v/jeroen-g/explorer.svg?style=flat-square [ico-actions]: https://img.shields.io/github/workflow/status/Jeroen-G/explorer/CI?label=CI%2FCD&style=flat-square diff --git a/docs/sorting.md b/docs/sorting.md new file mode 100644 index 0000000..433c584 --- /dev/null +++ b/docs/sorting.md @@ -0,0 +1,23 @@ +# Sorting + +By default, your search results will be sorted by their score according to Elasticsearch. +If you want to step in and influence the sorting you may do so using a simplified implementation in Explorer. +Currently it is only possible to define one sort order. + +```php +use App\Models\Post; +use \JeroenG\Explorer\Domain\Syntax\Sort; + +$results = Post::search('Self-steering') + ->sort(new Sort('published_at')) + ->get(); +``` + +The first parameter of a `Sort()` object is the name of the field, an optional second parameter is for the order. + +```php +use \JeroenG\Explorer\Domain\Syntax\Sort; + +new Sort('id', Sort::ASCENDING); // the default +new Sort('id', Sort::DESCENDING); +``` diff --git a/src/Application/BuildCommand.php b/src/Application/BuildCommand.php index cfd220f..179937d 100644 --- a/src/Application/BuildCommand.php +++ b/src/Application/BuildCommand.php @@ -4,6 +4,7 @@ namespace JeroenG\Explorer\Application; +use JeroenG\Explorer\Domain\Syntax\Sort; use Laravel\Scout\Builder; use Webmozart\Assert\Assert; @@ -25,6 +26,8 @@ class BuildCommand private ?int $limit = null; + private ?Sort $sort = null; + public static function wrap(Builder $builder): BuildCommand { $normalizedBuilder = new self(); @@ -34,6 +37,7 @@ public static function wrap(Builder $builder): BuildCommand $normalizedBuilder->setFilter($builder->filter ?? []); $normalizedBuilder->setWhere($builder->where ?? []); $normalizedBuilder->setQuery($builder->query ?? ''); + $normalizedBuilder->setSort($builder->sort ?? null); $index = $builder->index ?: $builder->model->searchableAs(); @@ -83,6 +87,20 @@ public function getLimit(): ?int return $this->limit; } + public function hasSort(): bool + { + return !is_null($this->sort); + } + + public function getSort(): array + { + if ($this->hasSort()) { + return $this->sort->build(); + } + + return []; + } + public function setMust(array $must): void { $this->must = $must; @@ -122,4 +140,9 @@ public function setLimit(?int $limit): void { $this->limit = $limit; } + + public function setSort(?Sort $sort = null): void + { + $this->sort = $sort; + } } diff --git a/src/Application/Finder.php b/src/Application/Finder.php index b43690e..aad8e56 100644 --- a/src/Application/Finder.php +++ b/src/Application/Finder.php @@ -50,6 +50,10 @@ public function find(): Results $query['size'] = $this->builder->getLimit(); } + if ($this->builder->hasSort()) { + $query['body']['sort'] = $this->builder->getSort(); + } + $rawResults = $this->client->search($query); return new Results($rawResults); diff --git a/src/Domain/Syntax/Sort.php b/src/Domain/Syntax/Sort.php new file mode 100644 index 0000000..3e3b0f7 --- /dev/null +++ b/src/Domain/Syntax/Sort.php @@ -0,0 +1,30 @@ +field = $field; + $this->order = $order; + Assert::inArray($order, [self::ASCENDING, self::DESCENDING]); + } + + public function build(): array + { + return [$this->field => $this->order]; + } +} diff --git a/src/ExplorerServiceProvider.php b/src/ExplorerServiceProvider.php index 214aa4b..4a01261 100644 --- a/src/ExplorerServiceProvider.php +++ b/src/ExplorerServiceProvider.php @@ -40,6 +40,11 @@ public function boot(): void $this->filters[] = $filter; return $this; }); + + Builder::macro('sort', function ($sort) { + $this->sort = $sort; + return $this; + }); } public function register(): void diff --git a/tests/Unit/BuildCommandTest.php b/tests/Unit/BuildCommandTest.php index d28609b..d7cf975 100644 --- a/tests/Unit/BuildCommandTest.php +++ b/tests/Unit/BuildCommandTest.php @@ -5,7 +5,9 @@ namespace JeroenG\Explorer\Tests\Unit; use Illuminate\Database\Eloquent\Model; +use InvalidArgumentException; use JeroenG\Explorer\Application\BuildCommand; +use JeroenG\Explorer\Domain\Syntax\Sort; use JeroenG\Explorer\Domain\Syntax\Term; use Laravel\Scout\Builder; use Mockery; @@ -88,4 +90,42 @@ public function buildCommandProvider(): array ['Query', 'Lorem Ipsum'], ]; } + + public function test_it_can_set_the_sort_order(): void + { + $command = new BuildCommand(); + + self::assertFalse($command->hasSort()); + + $command->setSort(new Sort('id')); + + self::assertTrue($command->hasSort()); + self::assertSame(['id' => 'asc'], $command->getSort()); + + $command->setSort(null); + + self::assertFalse($command->hasSort()); + self::assertSame([], $command->getSort()); + + $command->setSort(new Sort('id', 'desc')); + + self::assertTrue($command->hasSort()); + self::assertSame(['id' => 'desc'], $command->getSort()); + + $this->expectException(InvalidArgumentException::class); + $command->setSort(new Sort('id', 'invalid')); + } + + public function test_it_can_get_the_sorting_from_the_scout_builder(): void + { + $builder = Mockery::mock(Builder::class); + $builder->model = Mockery::mock(Model::class); + + $builder->index = self::TEST_INDEX; + $builder->sort = new Sort('id'); + + $subject = BuildCommand::wrap($builder); + + self::assertSame(['id' => 'asc'], $subject->getSort()); + } }