Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork5
ElasticScout is an optimized Laravel Scout driver for Elasticsearch 7.1+
License
renoki-co/elasticscout
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
ElasticScout is a Laravel Scout driver that interacts with any Elasticsearch server to bring the full-power of Full-Text Search in your models.
This package was shaped fromBabenko Ivan's Elasticscout Driver repo.
Renoki Co. on GitHub aims on bringing a lot of open source projects and helpful projects to the world. Developing and maintaining projects everyday is a harsh work and tho, we love it.
If you are using your application in your day-to-day job, on presentation demos, hobby projects or even school projects, spread some kind words about our work or sponsor our work. Kind words will touch our chakras and vibe, while the sponsorships will keep the open source projects alive.
Install the package using Composer CLI:
$ composer require rennokki/elasticscout
If your Laravel package does not support auto-discovery, add this to yourconfig/app.php file:
'providers' => [ ...Rennokki\ElasticScout\ElasticScoutServiceProvider::class, ...];
Publish the config files:
$ php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"$ php artisan vendor:publish --provider="Rennokki\ElasticScout\ElasticScoutServiceProvider"
If you wish to access directly the Elasticsearch Client, already set with the configuration of your own, you can do so by adding the facade toconfig/app.php:
'ElasticScout' =>Rennokki\ElasticScout\Facades\ElasticClient::class,
Then you can access it like you normally would:
// Get all indexesElasticScout::indices()->get(['index' =>'*']);
In your.env file, set youtSCOUT_DRIVER toelasticscout, alongside with Elasticsearch configuration:
SCOUT_DRIVER=elasticscoutSCOUT_ELASTICSEARCH_HOST=localhostSCOUT_ELASTICSEARCH_PORT=9200
Amazon Elasticsearch Service works perfectly fine without any additional setup for VPC Clusters. However, it is a bit freaky about Public clusters because it requires IAM authentication.
You will first make sure that you have the right version ofelasticsearch/elasticsearch package installed.For instance, for a7.4 cluster, you should installelasticsearch/elasticsearch:"7.4.*", otherwise you will receive errors in your application.
$ composer require elasticsearch/elasticsearch:"7.4.*"To find the right package size, check theElasticsearch's Version Matrix.
When requesting data from an AWS Elasticsearch cluster, ElasticScout makes sure your authentication will be passed to the further requests by attaching a handler. All you have to do is to enable the setup, by setting theSCOUT_ELASTICSCOUT_AWS_ENABLED env variable:
AWS_ACCESS_KEY_ID=my_keyAWS_SECRET_ACCESS_KEY=my_secretSCOUT_ELASTICSCOUT_AWS_ENABLED=trueSCOUT_ELASTICSCOUT_AWS_REGION=us-east-1SCOUT_ELASTICSEARCH_HOST=search-xxxxxx.es.amazonaws.comSCOUT_ELASTICSEARCH_PORT=443SCOUT_ELASTICSEARCH_SCHEME=https
Please keep in mind: you do not need user & password for AWS Elasticsearch Service clusters.
In Elasticsearch, the Index is the equivalent of a table in MySQL, or a collection in MongoDB. You can create an index class using artisan:
$ php artisan make:elasticscout:index PostIndex
You will have something like this inapp/Indexes/PostIndex.php:
namespaceApp\Indexes;useRennokki\ElasticScout\Index;useRennokki\ElasticScout\Migratable;class PostIndexextends Index{use Migratable;/** * The settings applied to this index. * * @var array */protected$settings = [// ];/** * The mapping for this index. * * @var array */protected$mapping = [// ];}
The key here is that you can set settings and a mapping for each index.You can find more on Elasticsearch's documentation website aboutmappings andsettings.
Here's an example on creating a mapping for a field that isa geo-point datatype:
class RestaurantIndexextends Index{ ...protected$mapping = ['properties' => ['location' => ['type' =>'geo_point', ], ], ];}
Here is an example on creating a new analyzer in the$settings variable fora whitespace tokenizer:
class PostIndexextends Index{ ...protected settings = ['analysis' => ['analyzer' => ['content' => ['type' =>'custom','tokenizer' =>'whitespace', ], ], ], ];}
If you wish to change the name of the index, you can do so by overriding the$name variable:
class PostIndexextends Index{protected$name ='posts_index_2';}
All the models that can be searched into should use theRennokki\ElasticScout\Searchable trait and implement theRennokki\ElasticScout\Index\HasElasticScoutIndex interface:
useRennokki\ElasticScout\Contracts\HasElasticScoutIndex;useRennokki\ElasticScout\Searchable;class Postextends Modelimplements HasElasticScoutIndex{use Searchable;}
Additionally, the model should also specify the index class:
useApp\Indexes\PostIndex;useRennokki\ElasticScout\Contracts\HasElasticScoutIndex;useRennokki\ElasticScout\Index;useRennokki\ElasticScout\Searchable;class Postextends Modelimplements HasElasticScoutIndex{use Searchable;/** * Get the index instance class for Elasticsearch. * * @return \Rennokki\ElasticScout\Index */publicfunctiongetElasticScoutIndex():Index {returnnewPostIndex($this); }}
To publish the index to Elasticsearch, you should sync the index:
$ php artisan elasticscout:index:sync App\\PostNow, each time your model creates,updates or deletes new records, they will be automatically synced to Elasticsearch.
In case you want to import already-existing data, please use thescout:import command that is described in the Scout documentation.
Syncing the index can also be done within your code:
$restaurant = Restaurant::first();$restaurant->getIndex()->sync();// returns true/false
To query data into Elasticsearch, you may use thesearch() method:
Post::search('Laravel') ->take(30) ->from(10) ->get();
In case you want just the number of the documents, you can do so:
$posts = Post::search('Lumen')->count();
ElasticScout allows you to create a custom query using built-in methods by going through theelasticsearch() method.
You can use Elasticsearch's must, must_not, should and filter keys directly in the builder.Keep in mind that you can chain as many as you want.
Post::elasticsearch() ->must(['term' => ['tag' =>'wow']]) ->should(['term' => ['tag' =>'yay']]) ->shouldNot(['term' => ['tag' =>'nah']]) ->filter(['term' => ['tag' =>'wow']]) ->get();
You can append data to body or query keys.
// apend to the body payloadPost::elasticsearch() ->appendToBody('minimum_should_match',1) ->appendToBody('some_field', ['array' =>'yes']) ->get();
// append to the query payloadPost::elasticsearch() ->appendToQuery('some_field','value') ->appendToQuery('some_other_field', ['array' =>'yes']) ->get();
Post::elasticsearch() ->where('title.keyword','Elasticsearch') ->first();
Book::elasticsearch() ->whereBetween('price', [100,200]) ->first();
Book::elasticsearch() ->whereNotBetween('price', [100,200]) ->first();
Book::elasticsearch() ->when(true,function ($query) {return$query->where('price',100); })->get();
Book::elasticsearch() ->unless(false,function ($query) {return$query->where('price',100); })->get();
Book::elasticsearch() ->wherePrice(100) ->get();// This is the equivalent.Book::elasticsearch() ->where('price',100) ->get();
If the dynamic where contains multiple words, they are split bysnake_case:
Book::elasticsearch() ->whereFanVotes(10) ->get();// This is the same.Book::elasticsearch() ->where('fan_votes',10) ->get();
Post::elasticsearch() ->whereRegexp('title.raw','A.+') ->get();
Since Elasticsearch has a NoSQL structure, you should be able to check if a field exists.
Post::elasticsearch() ->whereExists('meta') ->whereNotExists('new_meta') ->get();
Restaurant::whereGeoDistance('location', [-70,40],'1000m') ->get();
Restaurant::whereGeoBoundingBox('location', ['top_left' => [-74.1,40.73],'bottom_right' => [-71.12,40.01], ])->get();
Restaurant::whereGeoPolygon('location', [ [-70,40], [-80,30], [-90,20], ])->get();
Restaurant::whereGeoShape('shape', ['type' =>'circle','radius' =>'1km','coordinates' => [4,52], ],'WITHIN')->get();
Elasticscout also works with scopes that are defined in the main model.
class Restaurantextends Model{publicfunctionscopeNearby($query,$lat,$lon,$meters) {return$query->whereGeoDistance('location', [$lat,$lon],$meters.'m'); }}$nearbyRestaurants = Restaurant::search('Dominos')->nearby(45,35,1000)->get();
Query-by-query caching is available usingrennokki/laravel-eloquent-query-cache. All you have to do is to check the repository on how to use it.
Basically, you can cache requests like so:
$booksByJohnGreen = Book::elasticsearch() ->cacheFor(now()->addMinutes(60)) ->where('author','John Green') ->get();
A search rule is a class that can be used on multiple queries, helping you to define custom payload only once. This works only for the Search Query builder.
To create a rule, use the artisan command:
$ php artisan make:elasticscout:rule NameRule
You will get something like this:
namespaceApp\SearchRules;useRennokki\ElasticScout\Builders\SearchQueryBuilder;useRennokki\ElasticScout\SearchRule;class NameRuleextends SearchRule{/** * Initialize the rule. * * @return void */publicfunction__construct() {// }/** * Build the highlight payload. * * @param SearchQueryBuilder $builder * @return array */publicfunctionbuildHighlightPayload(SearchQueryBuilder$builder) {return [// ]; }/** * Build the query payload. * * @param SearchQueryBuilder $builder * @return array */publicfunctionbuildQueryPayload(SearchQueryBuilder$builder) {return [// ]; }}
Within thebuildQueryPayload(), you should define the query payload that will take place during the query.
For example, you can get started with some bool query. Details about the bool query you can findin the Elasticsearch documentation.
class NameRuleextends SearchRule{publicfunctionbuildQueryPayload(SearchQueryBuilder$builder) {return ['must' => ['match' => [// access the search phrase from the $builder'name' =>$builder->query, ], ], ]; }}
To apply by default on all search queries, define agetElasticScoutSearchRules() method in your model:
useApp\SearchRules\NameRule;class Restaurantextends Model{/** * Get the search rules for Elasticsearch. * * @return array */publicfunctiongetElasticScoutSearchRules():array {return [newNameRule, ]; }}
To apply the rule at the query level, you can call the->addRule() method:
useApp\SearchRules\NameRule;Restaurant::search('Dominos') ->addRule(newNameRule) ->get();
You can add multiple rules or set the rules to a specific value:
useApp\SearchRules\NameRule;useApp\SearchRules\LocationRule;Restaurant::search('Dominos') ->addRules([newNameRule,newLocationRule($lat,$lon), ])->get();// The rule that will be aplied will be only LocationRuleRestaurant::search('Dominos') ->addRule(newNameRule) ->setRules([newLocationRule($lat,$lon), ])->get();
When building the highlight payload, you can pass the array to thebuildHighlightPayload() method.More details on highlighting can be foundin the Elasticsearch documentation.
class NameRuleextends SearchRule{publicfunctionbuildHighlightPayload(SearchQueryBuilder$builder) {return ['fields' => ['name' => ['type' =>'plain', ], ], ]; }publicfunctionbuildQueryPayload(SearchQueryBuilder$builder) {return ['should' => ['match' => ['name' =>$builder->query, ], ], ]; }}
To access the payload, you can use the$highlight attribute from the model (or from each model of the final collection).
useApp\SearchRules\NameRule;$restaurant = Restaurant::search('Dominos')->addRule(newNameRule)->first();$name =$restaurant->elasticsearch_highlights->name;$nameAsString =$restaurant->elasticsearch_highlights->nameAsString;
In case you need to pass arguments to the rules, you can do so by adding your construct method.
class NameRuleextends SearchRule{protected$name;publicfunction__construct($name =null) {$this->name =$name; }publicfunctionbuildQueryPayload(SearchQueryBuilder$builder) {// Override the name from the rule construct.$name =$this->name ?:$builder->query;return ['must' => ['match' => ['name' =>$name, ], ], ]; }}Restaurant::search('Dominos') ->addRule(newNameRule('Pizza Hut')) ->get();
You can debug by explaining the query.
Restaurant::search('Dominos')->explain();
You can see how the payload looks like by callinggetPayload().
Restaurant::search('Dominos')->getPayload();
vendor/bin/phpunit
Please seeCONTRIBUTING for details.
If you discover any security related issues, please emailalex@renoki.org instead of using the issue tracker.
About
ElasticScout is an optimized Laravel Scout driver for Elasticsearch 7.1+
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors4
Uh oh!
There was an error while loading.Please reload this page.
