Introducing Laravel Scout
This is a series of posts on New Features in Laravel 5.3.
!Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.
Search tools ElasticSearch and Algolia have gained a lot of popularity in the Laravel community in the last few years as powerful tools for indexing and searching your data. Ben Corlett did a fantastic job introducing ElasticSearch at Laracon Eu 2014, and I wrote a pull request to Laravel.com introducing ElasticSearch-based indexing for the docs in 2015. But before my PR was merged, the folks at Algolia took it and updated it to instead use Algolia (faster and with a better UI!), and that's what you'll see today if you search the Laravel docs.
If you take a look at my pull request or theirs, you'll see that it's not a small task to integrate fulltext search into your site. Algolia has since released a free product called Algolia DocSearch that makes it easy to add an Algolia search widget to documentation pages. But for anything else, you're still stuck writing the integration yourself—that is, until now.
Introducing Laravel Scout
Scout is a driver-based fulltext search solution for Eloquent. Scout makes it easy to index and search the contents of your Eloquent models; currently it works with Algolia and ElasticSearch, but Taylor's asked for community contributions to other fulltext search services.
Scout is a separate Laravel package, like Cashier, that you'll need to pull in with Composer. We'll be adding traits to our models that indicate to Scout that it should listen to the events fired when instances of those models are modified and update the search index in response.
Take a look at this syntax for fulltext search, for finding any Review
with the word Llew
in it:
Review::search('Llew')->get();
Review::search('Llew')->paginate(20);
Review::search('Llew')->where('account_id', 2)->get();
All that with very little configuration. That's a beautiful thing.
Installing Scout
First, pull in the package (once it's live, and on a Laravel 5.3 app):
composer require laravel/scout
Next, add the Scout service provider (Laravel\Scout\ScoutServiceProvider::class
) to the providers
section of config/app.php
.
We'll want to set up our Scout configuration. Run php artisan vendor:publish
and paste your Algolia credentials in config/scout.php
.
Finally, assuming you're using Algolia, install the Algolia SDK:
composer require algolia/algoliasearch-client-php
Marking your model for indexing
Now, go to your model (we'll use Review
, for a book review, for this example). Import the Laravel\Scout\Searchable
trait. You can define which properties are searchable using the toSearchableArray()
method (it defaults to mirroring toArray()
), and define the name of the model's index using the searchableAs()
method (it defaults to the table name).
Once we've done this, you can go check out your Algolia index page on their web site; when you add, update, or delete Review
records, you'll see your Algolia index update. Just like that.
Searching your index
We took a look at this already, but here's a refresh of how to search:
// Get all records from the Review that match the term "Llew"
Review::search('Llew')->get();
// Get all records from the Review that match the term "Llew",
// limited to 20 per page and reading the ?page query parameter,
// just like Eloquent pagination
Review::search('Llew')->paginate(20);
// Get all records from the Review that match the term "Llew"
// and have an account_id field set to 2
Review::search('Llew')->where('account_id', 2)->get();
What comes back from these searches? A Collection of Eloquent models, re-hydrated from your database. The IDs are stored in Algolia, which returns a list of matched IDs, and then Scout pulls the database records for those and returns them as Eloquent objects.
You don't have full access to the complexity of SQL where
commands, but it handles a solid basic framework for comparison checks like you can see in the code samples above.
Queues
You can probably guess that we're now making HTTP requests to Algolia on every request that modifies any database records. That can make things slow down very quickly, so you may find yourself wanting to queue these operations—which, thankfully, is simple.
In config/scout.php
, set queue
to true
so that these updates are set to be indexed asynchronously. We're now looking at "eventual consistency"; your database records will receive the updates immediately, and the updates to your search indexes will be queued and updated as fast as your queue worker allows.
Special cases
Let's cover some special cases.
Perform operations without indexing
What if you want to perform a set of operations and avoid triggering the indexing in response? Just wrap them in the withoutSyncingToSearch()
method on your model:
Review::withoutSyncingToSearch(function () {
// make a bunch of reviews, e.g.
factory(Review::class, 10)->create();
});
Manually trigger indexing via code
Let's say you're now ready to perform the indexes, now that some bulk operation has been successfully performed. How?
Just add searchable()
to the end of any Eloquent query and it will index all of the records that were found in that query.
Review::all()->searchable();
You can also choose to scope the query to only those you want to index, but it's worth noting that the indexing will insert new records and update old records, so it's not bad to let it run over some records that may be indexed already.
This will also work on a relationship:
$user->reviews()->searchable();
You can also un-index any records with the same sort of query chaining, but just using unsearchable()
instead:
Review::where('sucky', true)->unsearchable();
Manually trigger indexing via CLI
There's an Artisan command for that.™
php artisan scout:import App\\Review
That'll chunk all of the Review
models and index them all.
Conclusion
That's it! With almost no work, you now have complete full-text search running on your Eloquent models.
Comments? I'm @stauffermatt on Twitter
Tags: laravel • laravel 5.3 • laravel scout
This is part of a series of posts on New Features in Laravel 5.3:
-
Jun 16, 2016 | laravel, laravel 5.3, echo, websockets
-
Jun 27, 2016 | laravel, laravel 5.3
-
Jun 29, 2016 | laravel, laravel 5.3, eloquent
-
Jul 6, 2016 | laravel, laravel 5.3
-
Jul 8, 2016 | laravel, laravel 5.3
-
Jul 8, 2016 | laravel, laravel 5.3, eloquent
-
Jul 25, 2016 | laravel, laravel 5.3
-
Jul 26, 2016 | laravel, laravel 5.3
-
Jul 27, 2016 | laravel, laravel 5.3, routing
-
Jul 27, 2016 | laravel, laravel 5.3, laravel scout, laravel passport, mailable
-
Jul 29, 2016 | laravel, laravel 5.3, laravel scout
-
Jul 30, 2016 | laravel, laravel 5.3, laravel passport, oauth
-
Aug 5, 2016 | laravel, laravel 5.3, mail, laravel mailables
-
Aug 8, 2016 | laravel, laravel 5.3
-
Oct 19, 2016 | laravel, laravel 5.3, laravel notifications, notifications
-
Dec 21, 2016 | laravel, laravel 5.3, vuejs, vueify, authorization
-
Dec 21, 2016 | laravel, laravel 5.3, queues
-
Jan 30, 2017 | laravel, laravel 5.3, artisan