New Package Laravel-Searchable: Easily Search in Multiple Models

Spatie team is still on fire with new packages. This week they released another one called Laravel Searchable, created mainly by AlexVanderbist. I’ve tried it myself and can show you a demo, along with my opinion.

What is Laravel Searchable

Spatie’s package makes searching in models an easy task, without external dependencies.

The main advantage, as I’ve tested it, is ability to perform mega-search in all project database, specifying more than one model to search in.

Here’s an example search code from Controller:

$searchResults = (new Search())
   ->registerModel(User::class, 'name')
   ->registerModel(BlogPost::class, 'title')
   ->perform('john');

Looks pretty simple and readable, right?

You would say there’s no need for another “search” package when we have Laravel Scout, Algolia, ElasticSearch and others, right? Here’s Freek Van der Herten’s official take on it:

  1. Main difference with Scout is that this one has no external dependencies.
  2. laravel-searchable does not try to replace Scout. Both packages have their place. Make your own decision what you need in your project!

Example mini-project: preparation

To test the package, I’ve created a fresh Laravel 5.7 project (the code will be available on GitHub – link at the end of article) with two database tables: categories and companies:

Schema::create('categories', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->timestamps();
});

Schema::create('companies', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name')->nullable();
    $table->unsignedInteger('category_id');
    $table->foreign('category_id')->references('id')->on('categories');
    $table->timestamps();
});

Also, seeded some data for both tables, used make:auth to generate a simple Bootstrap template, and ended up with this list of companies:

The code is really simple, here’s HomeController:

public function index()
{
    $companies = Company::with('category')->get();
    return view('home', compact('companies'));
}

Now, let’s say we want to search both Categories and Companies from one text field. This is where the package will help us.

Notice a Search bar in top-right corner? Here’s HTML code for it:

<form action="{{ route('search') }}" method="POST">
    @csrf
    <input type="text" name="query" />
    <input type="submit" class="btn btn-sm btn-primary" value="Search" />
</form>

Meanwhile, in routes/web.php, we have the homepage, search results, and pages for individual category/company:

Route::get('/', 'HomeController@index')->name('home');
Route::post('/search', 'HomeController@search')->name('search');
Route::get('/companies/{company}', 'CompanyController@show')->name('companies.show');
Route::get('/categories/{category}', 'CategoryController@show')->name('categories.show');

So, “all” we need to do now, is implement HomeController@search method. Now, step-by-step.


Example mini-project: using Laravel Searchable

Step 1. Install the package.

composer require spatie/laravel-searchable

That’s it, no more steps required at installation.

Step 2. Prepare the Models to be Searchable

This is where we need to do some manual work, in both app/Category.php and app/Company.php.

This is how Category model should look:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Category extends Model implements Searchable
{

    protected $fillable = ['name'];

    public function getSearchResult(): SearchResult
    {
        $url = route('categories.show', $this->id);

        return new SearchResult(
            $this,
            $this->name,
            $url
         );
    }

}

Let’s break it down, what changes have we made?

  • Using Spatie\Searchable\Searchable and then implements Searchable;
  • Using Spatie\Searchable\SearchResult and implementing method getSearchResult() which should return SearchResult object;
  • Within that SearchResult() object we need to specify three parameters: where are we searching (model itself – $this), what is the returned column for title (it will be displayed in results – so category name, $this->name), and what URL the result should link to (we’re building that with route() helper)

Really really similar transformations happen in app/Company.php:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;

class Company extends Model implements Searchable
{

    protected $fillable = ['name', 'category_id'];

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function getSearchResult(): SearchResult
    {
        $url = route('companies.show', $this->id);

        return new SearchResult(
            $this,
            $this->name,
            $url
        );
    }

}

Step 3. Performing the search from Controller

Here’s the code for our HomeController method:

namespace App\Http\Controllers;

use App\Category;
use App\Company;
use Illuminate\Http\Request;
use Spatie\Searchable\Search;

class HomeController extends Controller
{

    // ... other methods

    public function search(Request $request)
    {
        $searchResults = (new Search())
            ->registerModel(Company::class, 'name')
            ->registerModel(Category::class, 'name')
            ->perform($request->input('query'));

        return view('search', compact('searchResults'));
    }

}

As you can see, we’re creating a Search() object and registering TWO models with the fields that we need to search for. So that ‘name’ could be different for each model, it’s just a coincidence that it’s the same here.

Step 4. Viewing the Results: Grouped by Model

Here I’ve taken the example from the official documentation with a few small tweaks. Here’s our resources/views/search.blade.php:

... Some main Blade code here ...

<div class="card">
    <div class="card-header"><b>{{ $searchResults->count() }} results found for "{{ request('query') }}"</b></div>

    <div class="card-body">

        @foreach($searchResults->groupByType() as $type => $modelSearchResults)
            <h2>{{ ucfirst($type) }}</h2>

            @foreach($modelSearchResults as $searchResult)
                <ul>
                    <li><a href="{{ $searchResult->url }}">{{ $searchResult->title }}</a></li>
                </ul>
            @endforeach
        @endforeach

    </div>
</div>

... Some more main Blade template code ...

And here’s how it looks:

As you can see, only one Company is returned but not the Category. Now, let’s try a query with both results present:

Here’s how $searchResults structure looks like if we do dd():


We’ve covered a basic usage, but there are a few more customizable things in Laravel Searchable. You can read them in the official documentation.

As promised, here’s the link to Github repository of this demo-project.

What do you think? Will you use the package?

Like our articles?
Check out our Laravel online courses!

7 COMMENTS

    • Hi Nick, I guess you should raise this question as issue on github for the package, by default I don’t think these packages are compatible.

  1. Hi Korop

    Thankyou so much for the detailed information on this package

    Was working on a high pressure project and this article was exactly what i needed!

  2. Hi,
    i’d like to use this package, it look really powerfull, but i need to use it to show recommendations based on keywords found somewhere on the page content…
    Sooo i need to use it to get somes things from my models and display it on my page.

    (i dont need the $url parametter, all i need it the reschearch function)

    i’d like to get someting like this :
    random page content -> get KeyWords
    Searchable functions -> get things from models related to the keywords

    display evreything on the page loading

    All of this in one page, at the loading, without html input…

    is it possible ?

    • Update :

      i solved my problem by myself 🙂

      if someone is wondering how to do…

      simply remove all the ulr parametters from searchResult.php, in the model, juste remove url parametters too : (this is what i have)
      public function getSearchResult(): SearchResult
      {
      return new \Spatie\Searchable\SearchResult(
      $this,
      $this->name
      );
      }
      then on your controller, just use your function :

      public function searchIt()
      {
      $searchResults = (new Search())
      ->registerModel(Foo::class, ‘name’)
      ->search(“keywords”);
      return $searchResults;
      }

      it will return an array of results, and to display it on blade, use : $searchResult->searchable

      this work well for me 🙂

LEAVE A REPLY

Please enter your comment!
Please enter your name here