greensight / laravel-elastic-query
Requires
- php: ^8.0
- elasticsearch/elasticsearch: ^7.13
- illuminate/support: ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.16
- mockery/mockery: ^1.4
- orchestra/testbench: ^6.0
- php-parallel-lint/php-var-dump-check: ^0.5.0
- phpunit/phpunit: ^9.0
README
Deprecated, use https://github.com/ensi-platform/laravel-telemetry instead
Working with Elasticsearch in an Eloquent-like fashion.
Installation
You can install the package via composer:
composer require greensight/laravel-elastic-query
- Set
ELASTICSEARCH_HOSTS
in your.env
file.,
can be used as a delimeter.
Basic usage
Let's create and index class. It's someting like Eloquent model.
use Greensight\LaravelElasticQuery\ElasticIndex; class ProductsIndex extends ElasticIndex { protected string $name = 'test_products'; protected string $tiebreaker = 'product_id'; }
You should set a unique in document attribute name in $tiebreaker
. It is used as an additional sort in search_after
Now we can get some documents
$searchQuery = ProductsIndex::query(); $hits = $searchQuery ->where('rating', '>=', 5) ->whereDoesntHave('offers', fn(BoolQuery $query) => $query->where('seller_id', 10)->where('active', false)) ->sortBy('rating', 'desc') ->sortByNested('offers', fn(SortableQuery $query) => $query->where('active', true)->sortBy('price', mode: 'min')) ->take(25) ->get();
Filtering
$searchQuery->where('field', 'value'); $searchQuery->where('field', '>', 'value'); // supported operators: `=` `!=` `>` `<` `>=` `<=` $searchQuery->whereNot('field', 'value'); // equals `where('field', '!=', 'value')`
$searchQuery->whereIn('field', ['value1', 'value2']); $searchQuery->whereNotIn('field', ['value1', 'value2']);
$searchQuery->whereNull('field'); $searchQuery->whereNotNull('field');
$searchQuery->whereHas('nested_field', fn(BoolQuery $subQuery) => $subQuery->where('field_in_nested', 'value')); $searchQuery->whereDoesntHave( 'nested_field', function (BoolQuery $subQuery) { $subQuery->whereHas('nested_field', fn(BoolQuery $subQuery2) => $subQuery2->whereNot('field', 'value')); } );
nested_field
must have nested
type.
Subqueries cannot use fields of main document only subdocument.
Sorting
$searchQuery->sortBy('field', 'desc', 'max'); // field is from main document $searchQuery->sortByNested( 'nested_field', fn(SortableQuery $subQuery) => $subQuery->where('field_in_nested', 'value')->sortBy('field') );
Second attribute is a direction. It supports asc
and desc
values. Defaults to asc
.
Third attribute - sorting type. List of supporting types: min, max, avg, sum, median
. Defaults to min
.
There are also dedicated sort methods for each sort type.
$searchQuery->minSortBy('field', 'asc'); $searchQuery->maxSortBy('field', 'asc'); $searchQuery->avgSortBy('field', 'asc'); $searchQuery->sumSortBy('field', 'asc'); $searchQuery->medianSortBy('field', 'asc');
Pagination
Offset Pagination
$page = $searchQuery->paginate(15, 45);
Offset pagination returns total documents count as total
and current position as size/offset
.
Cursor pagination
$page = $searchQuery->cursorPaginate(10); $pageNext = $searchQuery->cursorPaginate(10, $page->next);
current
, next
, previous
is returned in this case instead of total
, size
and offset
.
You can check Laravel docs for more info about cursor pagination.
Aggregation
Aggregaction queries can be created like this
$aggQuery = ProductsIndex::aggregate(); /** @var \Illuminate\Support\Collection $aggs */ $aggs = $aggQuery ->where('active', true) ->terms('codes', 'code') ->nested( 'offers', fn(AggregationsBuilder $builder) => $builder->where('seller_id', 10)->minmax('price', 'price') ); $aggs
Type of $aggs->price
is MinMax
.
Type of $aggs->codes
is BucketCollection
.
Aggregate names must be unique for whole query.
Aggregate types
Get all variants of attribute values:
$aggQuery->terms('agg_name', 'field');
Get min and max attribute values. E.g for date:
$aggQuery->minmax('agg_name', 'field');
Aggregation plays nice with nested documents.
$aggQuery->nested('nested_field', function (AggregationsBuilder $builder) { $builder->terms('name', 'field_in_nested'); });
There is also a special virtual composite
aggregate on the root level. You can set special conditions using it.
$aggQuery->composite(function (AggregationsBuilder $builder) { $builder->where('field', 'value') ->whereHas('nested_field', fn(BoolQuery $query) => $query->where('field_in_nested', 'value2')) ->terms('field1', 'agg_name1') ->minmax('field2', 'agg_name2'); });
Query Log
Just like Eloquent ElasticQuery has its own query log, but you need to enable it manually
Each message contains indexName
, query
and timestamp
ElasticQuery::enableQueryLog(); /** @var \Illuminate\Support\Collection|Greensight\LaravelElasticQuery\Debug\QueryLogRecord[] $records */ $records = ElasticQuery::getQueryLog(); ElasticQuery::disableQueryLog();
Contributing
Please see CONTRIBUTING for details.
Testing
- composer install
- npm i
- Start Elasticsearch in your preferred way.
- Copy
phpunit.xml.dist
tophpunit.xml
and set correct env variables there - composer test
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
License
The MIT License (MIT). Please see License File for more information.