I am currently using Laravel Scout to search through my models with the database driver
My search controller looks like this
Post::search($request->filter)->get();
Lets say I have a posts with a Text that say "Hello this is a Post"
When I am searching for "Hello" or "Post" individually it works. But when I am searching for "Hello Post" together it doesnt because there are other words in between and the executed query column WHERE LIKE %Hello Post%
which is called by the search method is not finding matches. Is there a way to check if the text in the model contains all words of my query string regardless of their order and appearence? For me it seems like I must find a way to make a query like column WHERE LIKE %Hello% AND column WHERE LIKE %Post%
.
I tried to convert the search query into an array separated by empty space like explode(' ', $request->filter)
. However the search method on the model does not accept an Array. Only a string. I tried to loop and search over the elements of each array item individually like
foreach(explode(' ', $request->filter) as $filter) {
$results = Post::search($filter)->get();
}
but its giving me the wrong results because I need the post where all words fit and not just individual ones. I have considered to filter the results with Str::containsAll($column, explode(' ', $request->filter)
but this is not the desired solution.
I need a way to make the correct Database query as mentioned above like column WHERE LIKE %Hello% AND column WHERE LIKE %Post%
but how do I achieve this with Laravel Scout?
2
Answers
Modify the Search Query:
In this case you can ensures that the SQL executed behind the scenes would look like this:
But to be sure Test the search functionality:
Single word searches, Searches with common words and Empty search inputs.
But if you notice the search is slow:
Take a look if your database text columns are indexed appropriately. However, note that LIKE ‘%word%’ queries do not generally benefit from traditional indexes. But take a look anyway.
Consider use full-text search capabilities if your database supports them (like MySQL’s FULLTEXT index or PostgreSQL’s text search features). Laravel Scout can be configured to use these features if set up correctly.
Using raw LIKE queries might be inefficient for large datasets or columns. In such cases, consider switching to a more robust search driver supported by Laravel Scout, such as Algolia, MeiliSearch, or even database-specific text search.
And using a caching solution like Redis can significantly improve the performance of your search functionality.
Adding it to make after your comment:
To make sure the solution works with any Scout driver, you can create a Custom Search Method:
and so use custom search in Controller:
Unfortunately, the database driver that ships with Laravel Scout doesn’t support what you’re trying to do because at the end of the day, this driver is only a wrapper around the
WHERE LIKE
statement in MySQL, so when you feed it, for example, the queryHello Post
, it will yield the following SQL statement as you said:However, there’s a solution but it’s a little bit complicated, but I’m going to explain it in detail.
The solution would be to create a custom scout driver to alter this behavior. Don’t worry, it’s harder than it looks.
Scout uses the concepts of engines to manage search drivers, in this link you can view a set of available drivers in Laravel Scout. When you set the
SCOUT_DRIVER
variable to bedatabase
you’re telling Scout to use theDatabaseEngine
class for all its underlying operations.Inside this class, we can see the following:
search()
methodsearch()
method calls asearchModels()
methodsearchModels()
method calls abuildSearchQuery()
methodbuildSearchQuery()
calls aninitializeSearchQuery()
methodAs you can see, whatever search query you pass to the
search()
method on your model will be passed down here in the end, so what we’ll need to do is to alter this behavior a little bit by splitting the incoming search query using theexplode()
method and the whitespace as the separator, trim the resultant keywords, and join them using theimplode()
method again but use the%
operator as the separator.Create a
CustomDatabaseEngine
class inside yourapp
folder, this class should extend theLaravelScoutEnginesDatabaseEngine
class so that we keep all the benefits of the original database driver, and remember that we’re only interested in overriding theinitializeSearchQuery()
method. Here’s a suggested implementation:Now you need to register this custom driver, this can be done using the
extend()
method offered by theLaravelScoutEngineManager
class. Inside theboot()
method of yourAppServierProvider
class, you can do this, and here’s an example:The first parameter of the
extend()
method will be the name of the driver, and the second defines the binding class of this driver. Now open up your.env
file and change theSCOUT_DRIVER
tocustom_database
:And that’s it, now if you have a column that contains the value
Hello World This Is My Post
and you try to run the code:You should get the desired result.
If you have any questions or need any clarification, ask me in the comments.