An indexing and querying package to create, maintain and search an elastic instance with Laravel Scout as backbone.
Larelastic\Elastic\Providers\ElasticServiceProvider::class
'Elastic' => Larelastic\Elastic\Facades\Elastic::class
php artisan vendor:publish
ELASTICSEARCH_HOST=http://elastic:9200
ELASTICSEARCH_PORT=9200
SCOUT_DRIVER=elastic
SCOUT_ELASTIC_HOST=http://elastic:9200
(for a completely new installation -- skip ahead past 3rd step if index exists)
Since this package relies on the existence of models and index configurators to create, search and maintain indices, you must run the below commands to generate and set up the appropriate objects.
1.) create an indexConfigurator model (in this example, "Tattoo"):
php artisan make:index-configurator TattooIndexConfigurator
2.) create your searchable Model:
php artisan make:searchable-model Product --index-configurator=TattooIndexConfigurator
php artisan elastic:create-index "App\Models\Tattoo"
4.) add data to your index
php artisan scout:import "App\Models\Tattoo"
php artisan elastic:migrate "App\MyModel" my_index_v2
This process creates a new index, imports all data from previous to this index, and then links the old index to this new one via an alias
php artisan elastic:update-mapping "App\\Models\MyModel"
php artisan elastic:update-index "App\Models\MyModelConfigurator"
php artisan elastic:drop-index "App\Models\MyModel"
In your designated model, you may want to make use of the method:
public function searchableQuery()
{
return $self->newQuery();
}
This will enable you to fine tune the actual query that is sent to elastic if you need to eager load or specify relationships.
Where/OrWhere queries make use of the bool methods (must
, must_not
, and should
). Syntax is meant to be eloquent-like, but must use the search
trait to differentiate it from actual Eloquent.
Operator | Function | Usage |
---|---|---|
= | equals | ->where('col', '=', 'value') ->where('col', 'value') |
<> | not equals | ->where('col', '<>', 'value') |
in | in field array | ->where('col', 'in', ['value']) |
begins_with | search phrase beginning matched to search | ->where('col', 'begins_with', 'value') |
ends_with | search phrase ending matched to search | ->where('col', 'ends_with', 'value') |
contains | search phrase found in field | ->where('col', 'contains', 'value') |
gte | >= | ->where('col', 'gte', 'value') |
lte | <= | ->where('col', 'lte', 'value') |
gt | > | ->where('col', 'gt', 'value') |
lt | < | ->where('col', 'lt', 'value') |
between | date found between two dates | ->where('col', 'between', ['value1', 'value2']) |
exists | the value is not null and is found in index | ->where('col', 'exists') |
$response = Tattoo::search()->where('name', 'mold')->get();
$response = Tattoo::search()->where('name', '=', 'mold')->get();
Both of the above queries are valid. Default operator is '=' unless provided.
$response = Tattoo::search()->orWhere(['name', '=', 'mold'],['name', '=', 'Element'])->get();
takes two arrays, makes use of the should
bool operator from Elastic. Evaluates to "find this OR that".
$response = Tattoo::search()->orWhere(function($query, $boolean){
$query->where('name', '=','Mold', $boolean)
->where('model', '=','test', $boolean);
return $query;
})->orWhere(function($query, $boolean){
$query->where('name', '=','element', $boolean)
->where('model', '=','test', $boolean);
return $query;
})->get();
Closure, makes use of multiple groupings under should
bool operator from Elastic. Evaluates to "find (this AND that) OR (this and that)".
$response = Tattoo::search()->whereMulti(['name', 'model'],'=','Mold')->get();
This makes use of the multimatch elastic function, takes an array of fields. Default search type is phrase prefix, but accepts an argument to override, such as best_fields:
$response = Tattoo::search()->whereMulti(['name', 'model'],'=','Mold', 'best_fields')->get();
$response = Tattoo::search()->whereMulti(['name', 'model'],'=','Mold')->paginate(20);
Pagination functions much the same as in eloquent. It will wrap your response in the Paginator Length Aware metadata fields:
first_page_url: "http://myProject.com?page=1",
from: 1,
last_page: 2,
last_page_url: "http://myProject.com?page=3",
next_page_url: "http://myProject.com?page=2",
path: "http://myProject.com",
per_page: 20,
prev_page_url: null,
to: 2,
total: 36
Simple Sorting:
$response = Tattoo::search()->where('name', 'mold')
sort->('name','desc')->get()
Sorting defaults to asc and can be stacked for multiple sorts.
If you need to specify a field_type for the sort field (i.e. 'keyword' or 'raw') you can include it as a param:
$response = Tattoo::search()->where('name', 'mold')
sort->('name', 'desc', 'keyword')->get()
The above may be necessary in the event you are already querying a nested object and want to ensure the datatype is listed separately.
Sorting by distance based on latitude and longitude. Sample query in Elastic:
{
"sort": [{
"_geo_distance": {
"location": {
"lat": 40,
"lon": -70
},
"order": "asc",
"unit": "mi",
"mode": "min",
"distance_type": "arc",
"ignore_unmapped": true
}
]}
}
In order to use this in the package, you can use the following syntax:
$data = [
'field' => 'location_lat_long',
'lat' => $lat,
'long' => $lat,
];
$this->search->buildGeoSort($data);
There is also support to return the distance query as raw syntax, which can be used in the Top Level OR statement. An example:
$latLongArray = explode(",", $this->user->location_lat_long);
$response['bool']['must'] = $this->search->whereDistanceSyntax('artist.location_lat_long', $latLongArray[0], $latLongArray[1], '25mi');
return $response;
Valid fields to pass in the data array are above in the elastic query.
Valid arguments for unit include: mi (miles), in (inches), yd (yards), km (kilometers), cm (centimeters), mm (millimeters).
There are two distance calculation modes: arc (the default), and plane. The arc calculation is the most accurate.
{
```php
mi (miles), in (inches), yd (yards), km (kilometers), cm (centimeters), mm (millimeters).
#### <a id="aggs"></a>Aggregation (aggs)
```php
$response = Tattoo::search()->where('name', 'mold')
->rollup('card_color')->get();
initial support for aggs takes a column name and creates buckets to count all matches. result set will be under an "aggs" object, like so:
{
aggs: {
agg_field: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [{
key: "G",
doc_count: 35
},
{
key: "B",
doc_count: 1
}
]
}
}
}