Featured Image

Eloquent model search in Laravel

For me, this is a very confusing topic because if you ask 10 developers how to build a search functionality in Laravel, you will most likely get at least 10 different answers. In this article, I am doing my best to simplify the problem and give you the best options to choose from.

Vanilla Laravel  

So the first option will be vanilla Laravel with no packages, just PHP (the laravel way) and Eloquent queries. That's ok for a simple search job or it is ok if you have a team of developers that can afford the luxury to spend a lot of time building a really complex search engine (if needed of course). The problem with plane php / laravel is that you will end up with these search classes and methods hard to maintain if your app will scale in the near future and the search needs become more and more complex.  Let me show you a basic example of building a search query:

So first things first. We will need to get a request from the input and compare that with our database tables/columns. So let's say we have something like this:

customers/index.blade.php

<form action="{{ route('customersSearch') }}" method="GET">
    @csrf
    <input type="text" name="query" placeholder="Type in and search...">
    <button type="submit" value="search"></button>
 </form>

Now we have to get the request and query the database from the controller or in its own class. In this example I am creating a search() method inside of the CustomersController like so:

public function search(Request $request) {

  $query = $request->input('query');

  $customers = Customers::where(DB::raw("CONCAT(`FirstName`, ' ' ,`LastName`)"), 'like', "%$query%")
                            ->orWhere(DB::raw("CONCAT(`LastName`, ' ' ,`FirstName`)"), 'like', "%$query%")
                            ->orWhere('id', 'like', "%$query%")
                            ->orWhere('Phone', 'like', "%$query%")
                            ->orWhere('Address', 'like', "%$query%")
                            ->orderBy('id', 'asc')
                            ->paginate(10);

    return view('customers.search')->with('customers', $customers);

}

routes/web.php

Route::get('search', 'CustomersController@search')->name('customersSearch');

Ok, so that is pretty much it. We have a functional search method and we display the search results in the search view. As you may notice the search() method already started to look busy and it's one step away to become unreadable... Yes, you can have a search class and use it everywhere within the app. Maybe that's a good idea but not in my case, an indie developer building his own apps, because that's more code to me to maintain and debug in the future.

What you see in the code above is just the beginning of a complete search method... you need (as you can already see), to CONCAT lots of columns to check different scenarios. Yes yes, you can do that using SWITCH bla bla bla, create filter() method to be applied when querying the database etc... I still don't like it, in my case at least I see this as more code (bugs) that I have to write and maintain. I want to keep simple to make my life easy in the future.

A simpler and better solution

A nice way to solve this is via a package. And again there are tons of packages out there to help you out but one that I think it is by far the simple one to implement in your application is nicolaslopezj/searchable. I like this package because it is scalable, you can always perform these multi-columns AND models search, perform crazy eloquent joins between the models and so on. It also lets you add "weight" on the search by specifying what columns whiting a table are more important than others. 

Installation:

composer require "nicolaslopezj/searchable": "1.*"

Add the trait to your model:

use Nicolaslopezj\Searchable\SearchableTrait;

Now inside of your model's class scope specify that you want to use the SearchableTrait:

use SearchableTrait;

Ok, now we have to specify the search rules we want to be applied:

  protected $searchable = [
        /**
         * Columns and their priority in search results.
         * Columns with higher values are more important.
         * Columns with equal values have equal importance.
         *
         * @var array
         */
        'columns' => [
            'customers.first_name' => 10,
            'customers.last_name' => 10,
            'customers.bio' => 2,
            'customers.email' => 5,
            'customers.title' => 2,
        ],

    ];

That's it, we just added a more complex and scalable search functionality to our codebase without spending weeks refining a search engine from scratch. Now, we can also perform joins so we can search in more models at once:

'joins' => [ 'posts' => ['users.id','posts.user_id'], ],

Now you can use the Model::search($query) to return the search results:

$customers = Customers::search($query)->orderBy('FirstName', 'asc')->paginate(10);
return view('customers.search')->with('customers', $customers);

That's it. Go ahead and give it a try. I think you'll like it. Another way of doing this is by using Spatie's package which is a very well written one.

Now, if you want something more complex and with a better user experience out of the box, you can take a driver-based search approach using Algolia. I will make a tutorial about using Algolia with Scout / Scout Extended in laravel in the following days. 

I will also want to talk and make a short demo about/with Laravel Livewire... That's what I'll be using in the app I'm building at the moment and it's easier than you can imagine.

Stay tuned!


Spread the word:

Sunday 03 May 2020 Marian V. Pop

Comments

What can I <code> for you ?

Drop me a line at me@mvpop.co.uk. I'd love to hear from you.

đź’Ś Send Email