Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Loomix's avatar

Best way to implement site wide search function?

It's easy to provide a simple search function like for instance $games = Game::where('name','like','%'.$s.'%')->get(); but I need it a bit more complex in terms of multiple search terms like "warcraft pc blizzard" (which is supposed to be handled like "warcraft AND pc AND blizzard", no need for OR) and the Game model has about 10+ relations of any kind to other tables like Publisher and Studio which are supposed to be searchable as well.

My initial idea is to create a view in the DB to use it for search operations but I am not sure of that, because I can't tell the performance difference to a load of Eloquent queries. Moreover, maybe Laravel provides some kind of out of the box functionality for site search?

0 likes
15 replies
LaryAI's avatar
Level 58

One possible solution is to use Laravel Scout, which is a package that provides full-text search functionality for Eloquent models. It uses Algolia or Elasticsearch as the search engine, and allows for complex search queries with multiple search terms and filters.

To use Scout, you need to install the package and configure the search engine driver. Then, you can add the Searchable trait to your Eloquent models and define the searchable fields. For example:

use Laravel\Scout\Searchable;

class Game extends Model
{
    use Searchable;

    public function toSearchableArray()
    {
        $array = $this->toArray();

        $array['publisher'] = $this->publisher->name;
        $array['studio'] = $this->studio->name;

        return $array;
    }
}

In this example, the Game model has relations to Publisher and Studio, and we want to include their names in the search index. The toSearchableArray method defines the fields that should be indexed and searchable.

To perform a search query, you can use the search method on the model:

$games = Game::search('warcraft pc blizzard')->get();

This will return a collection of Game models that match the search query. You can also use filters and sorting to refine the results.

Using Scout can improve the performance of site-wide search, especially for large datasets and complex queries. However, it requires additional setup and configuration, and may incur additional costs for the search engine service.

1 like
Loomix's avatar

Seems to be a great package but after following the docs at https://laravel.com/docs/9.x/scout and doing a test search with $games = Game::search('warcraft pc blizzard')->get();, $array = $this->toArray(); in toSearchableArray() only contains a column I appended in the model. There's no column of my DB in, so even e.g. $this->id is null. What am I missing?

dcx's avatar

@Loomix not really sure what your question is, you have a string with space chars - do you want to split it by space and search several models or just search for that string in many models? or take part of the string in specific models even? anyhow.. manually if there are many relations you could do something along the lines of the below...

No Idea if that helps or not ..

 ->orWhereRelation('project', 'name', 'like', '%' . request('searchText') . '%')
dcx's avatar

@Loomix to elaborate on my previous example.. just in case

    public function myExampleFunction($searchBy)
    {
        return Game::with(['compType','gameVersion'])
        ->where('name', 'like', '%' . $searchBy . '%')
        ->orWhereRelation('compType','type',  'like', '%' . $searchBy . '%')
        ->orWhereRelation('gameVersion', 'version', 'like', '%' . $searchBy . '%')
    }
Loomix's avatar

@dcx Thanks, I know how to implement it with orWhereRelation etc. My question is what the best practise is and the most performant way. Now that the AI has recommended Scout this seems to be a good way to go but it's not working, see my last question.

1 like
dcx's avatar

@Loomix well scout to be fair i never used so i cannot help much with that, but it really depends on the size of your search, there is also algolia (paid) etc but if your search is always relatively small the for me best practice is always simplicity - however i totally support using other tools when required.

Scout from what i can see though is searching columns on models so indeed you need to make the required columns searchable on all relevant models then it might work.. i don't know, the problem is still not clear to be honest - if you're searching multiple models they all need to use searchable and you have to add the columns to searchableArray in each model... otherwise sorry i couldn't help..

Loomix's avatar

Okay, let's take a look at my model which is supposed to be searchable:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Game extends Model
{
  use Searchable;
  
  protected $appends = [ 'release_style' ];

  public function publisher()
  {
    return $this->belongsTo(Publisher::class);
  }

  public function toSearchableArray()
  {
    $array = $this->toArray();
dump($array); 
  $array['publisher'] = $this->publisher->name;
  // -> leads to "Attempt to read property "name" on null"
    return $array;
  }

  public static function getSearchResult($searchTerms)
  {
  	$games = Game::search($searchTerms)->get();
    return $games;
  } 

The dump on $this->toArray() shows:

array:1 [
  "release_style" => ""
]

and leads to error "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'games.release_style' in 'where clause'", because it's just an append. I have no idea why Scout only sees the appended column and why it does not see the relation to Publisher. All the relations work fine everywhere at my site.

I installed Scout by composer require laravel/scout and php artisan vendor:publish –provider="Laravel/Scout/ScoutServiceProvider" and set 'driver' => env('SCOUT_DRIVER', 'database'), in scout.php.

dcx's avatar

@Loomix For me you've given me a nice thing to check out over the weekend so thanks for that, as i said before i've not used scout - and to be fair you're looking for reasons to complicate things really - and i mean that in the best way - best practice on things like this is ease of sorting out the code a year from now when you've never seen it etc - simplicity ...but that aside maybe this line

protected $appends = [ 'release_style' ];

is causing the problem.. cannot be sure but try removing it

Loomix's avatar

The append is surely not causing the problem and it's there for a reason. Scout is supposed to deal with appends. The issue is that Scout does not see my model right and all it's relations. I'm sure it's just something simple, like a missing import or stg. like that. I googled a lot about that but did not find anything. Let me know if you tested it in your environment, I'm really curious if it works.

psrz's avatar

Scout, with Algolia or Elasticsearch, can be overkill for most cases.

What are you using ? Mysql ? Postgresql ?

Loomix's avatar

I'm using MySQl 8 with PHP 8, Laravel 9 and the search results are displayed in DataTables 1.10.

Scout, with Algolia or Elasticsearch, can be overkill for most cases.

Well, the Scout doc says

If your application interacts with small to medium sized databases or has a light workload, you may find it more convenient to get started with Scout's "database" engine.

...so I think it's worth a try. I really like the Scout syntax here, it seems to be very intuitive.

Loomix's avatar

Bit of a progress here: When using

  public function toSearchableArray()
  {
    $array = $this->only('name', 'release');

I'm getting no errors and correct search results. However, while searching for either name or release brings correct results, searching for 2 terms like e.g. warcraft 1995 gives no results at all while the record is obviously in the DB.

Someone had the exact same issue with the database engine and gave up in the end, using plain Eloquent for searching, see https://stackoverflow.com/questions/75002422/laravel-scout-search-in-multiple-columns-at-once

The issue with appends registering as DB column is a bug which is known for about 5 years now and hasn't been fixed yet, see https://github.com/yabhq/laravel-scout-mysql-driver/issues/66

I'm a bit confused about Scout. It's an official Laravel package and does not seem to get a lot of love from the devs and it does not seem too popular in this community -- at least this is my impression for now.

So what you guys say:

  • Going the Eloquent-only way or try to mess with another engine like Elastic?
  • How about my initial idea to create a view for searching with plain Eloquent?
  • Would it make sense to use Queues for a site wide search?
newbie360's avatar
Level 24

@loomix

The method toSearchableArray() is for update data to the index file, actually you are searching the index file, not the database

be careful update() won't update the index file, use save() instead

// App\Models\Post

    public function searchableAs()
    {
        return 'project01_posts_index';
    }

    public function toSearchableArray()
    {
        return [
            'id' => (int) $this->id, // id should be always added to the index file
            'title' => $this->title,
            'created_at' => $this->created_at->toDateTimeString(),
            'updated_at' => $this->updated_at->toDateTimeString(),
            'post_comments' => $this->postComments()->get('comment_body'),
        ];
    }

setup index

// config/scout.php

    'meilisearch' => [
        'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
        'key' => env('MEILISEARCH_KEY', null),
        'index-settings' => [
            Post::class => [
                'searchableAttributes'=> [
                    'title',
                    'post_comments',
                ],
                'filterableAttributes'=> [
                    'id',
                    'created_at',
                    'updated_at',
                ],
                'sortableAttributes' => [
                    'id',
                    'created_at',
                    'updated_at',                
                ],
            ],
        ],

import data to index file, in dev most of time i need to use php artisan migrate:fresh --seed

so i added the code block to the seeder

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call([
            TagSeeder::class
            FakePostSeeder::class,
        ]);

        // Refresh Scout index file
        if (app()->isLocal()) {
            Artisan::call('scout:delete-index', ['name' => (new Post)->searchableAs()]);
            // Artisan::call('scout:flush', ['model' => "App\Models\Post"]);
            Artisan::call('scout:import', ['model' => "App\Models\Post"]);
            Artisan::call('scout:sync-index-settings');
        }
    }
}

btw, i was use Homestead with enabled meilisearch in Homestead.yaml

features:
    - mysql: true
    - mariadb: false
    - postgresql: false
    - ohmyzsh: false
    - webdriver: false
    - meilisearch: true

so i can access the demo search page via http://project01.test:7700

if want to check the index setting, go to http://project01.test:7700/indexes/....the-index-name..../settings

1 like
Loomix's avatar

@newbie360 Thanks a lot! Actually I wanted to use the "database" driver of Scout but I'll give this a try for sure.

@dcx How was your testing over the weekend?

dcx's avatar

@Loomix Hi there, sorry for delay I got a bit distracted in the end with work, however I just took another look and I think - nb: this is a pure guess and a little hacky, but you could make it a helper action or something just to formatSearchRequests or whatever...

Basically If you include this line from the stack overflow post to format your search string then it might work so like

$searchString = "%" . str_replace(" ", "%", request("yourPassedInString")) . "%";

Would obviously produce

%warcraft%1995%

So then under the hood when scout does it's where like search on each column it should find a record in both name and release columns i think...if that's what you're still after..

Please or to participate in this conversation.