sebastiansulinski/laravel-search

A lightweight search component for Laravel 11+.

v0.1.10 2024-11-21 10:30 UTC

This package is auto-updated.

Last update: 2024-11-21 10:30:47 UTC


README

This package provides a simple, lightweight search component for Laravel 11+.

In contrast to the well known Laravel Scout, it allows models to be associated with more than one index with separate payload for each, which was the much needed feature for my current project.

Currently only typesense driver is available, but if you feel like contributing a different implementation feel free to submit a PR.

Installation

composer require sebastiansulinski/laravel-search

Configuration

Start by publishing vendor configuration file search.php

php artisan vendor:publish --provider="SebastianSulinski\Search\SearchServiceProvider"

Within the configuration file update parameters for your selected driver and add all models to the models array:

'models' => [
    App\Models\Book::class,
    App\Models\Movie::class,
]

You can also update the default queue the import job will run on using SEARCH_QUEUE_CONNECTION environment variable - otherwise the default queue will be used.

Typesense configuration

Add the following to your services.php config file and update accordingly:

'typesense' => [
    'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
    'nodes' => [
        [
            'host' => env('TYPESENSE_HOST', 'localhost'),
            'port' => env('TYPESENSE_PORT', '8108'),
            'path' => env('TYPESENSE_PATH', ''),
            'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
        ],
    ],
    'nearest_node' => [
        'host' => env('TYPESENSE_HOST', 'localhost'),
        'port' => env('TYPESENSE_PORT', '8108'),
        'path' => env('TYPESENSE_PATH', ''),
        'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
    ],
    'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
    'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
    'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
    'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
],

Models

Each of the models defined within the configuration file under models array has to implement IndexableDocument and use SearchIndexable trait.

use Illuminate\Database\Eloquent\Model;
use SebastianSulinski\Search\IndexableDocument;
use SebastianSulinski\Search\SearchIndexable;

class Book extends Model implements IndexableDocument
{
    // ...
    use SearchIndexable;

You will also need to implement two methods:

searchableAs

This method returns a list of indexes the record should be listed under.

public static function searchableAs(): array
{
    return ['global_search'];
}

toSearchableArray

This method returns the payload in the form of array with the key representing corresponding index.

public function toSearchableArray(): array
{
    return [
        'global_search' => [
            'type' => 'book',
            'id' => $this->getSearchKey(),
            'name' => $this->name,
            'author' => $this->author,
            'description' => $this->description,
            'created_at' => $this->created_at->timestamp,
        ],
    ];
}

You can also overwrite the shouldBeSearchable method to indicate whether the record should be indexed.

Register search request parameters for your given implementation

If you are using the built-in controller with the SearchRequest, within your AppServiceProvider::boot method add all relevant validation rules for a given index. These will be used with the Indexer::search method - if you are using different approach for the search, you can ignore these. By default, only index and params (as array) are validated - whatever you define via this method will be merged to it.

\SebastianSulinski\Search\Facades\Search::validation('global_search', fn (\Illuminate\Foundation\Http\FormRequest $request) => [
    'params.q' => [
        'nullable',
        'string',
    ],
    'params.query_by' => [
        'required',
        'string',
    ],
    'params.highlight_fields' => [
        'nullable',
        'string',
    ],
    'params.facet_by' => [
        'nullable',
        'string',
    ],
    'params.filter_by' => [
        'nullable',
        'string',
    ],
    'params.page' => [
        'nullable',
        'integer',
    ],
    'params.per_page' => [
        'nullable',
        'integer',
    ],
    'params.sort_by' => [
        'nullable',
        'string',
    ],
]);

Disable default routes

To disable default routes add the following to your AppServiceProvider::register method:

\SebastianSulinski\Search\Facades\Search::withoutRoutes();

Import records

The following command will import all records for all indexes using default queue.

php artisan app:import-search

You can also specify the index you'd like to import:

php artisan app:import-search global_search

Remove index and all records

To remove the index and all its records use the following command:

php artisan app:purge-search global_search

Contributions

Contributions are welcome, but please make sure your code contains all necessary bells and whistles - pint it etc.