maize-tech / laravel-searchable
Laravel Searchable
Requires
- php: ^8.1
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- spatie/laravel-package-tools: ^1.14.1
Requires (Dev)
- larastan/larastan: ^2.9|^3.3
- laravel/pint: ^1.14
- orchestra/testbench: ^8.22|^9.2|^10.0|^11.0
- pestphp/pest: ^2.34|^3.7|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel Searchable 🔍
Easily add weighted searches through model attributes and relationships.
This package currently supports MySQL, PostgreSQL and SQLite (see SQLite support for its requirements and differences).
Installation
You can install the package via composer:
composer require maize-tech/laravel-searchable
You can publish the config file with:
php artisan vendor:publish --provider="Maize\Searchable\SearchableServiceProvider" --tag="searchable-config"
This is the content of the published config file:
return [ /* |-------------------------------------------------------------------------- | Default match weight |-------------------------------------------------------------------------- | | The weight of all searched words which match at least one of the | list of searchable attributes. | Defaults to 1. | */ 'default_match_weight' => 1, ];
Usage
To use the package, add the Maize\Searchable\HasSearch trait to each model you want to make searchable.
Once done, you can implement the getSearchableAttributes abstract method by returning the list of attributes (or relationships' attributes) you want to search for.
You can also define the weight of each searchable attribute. If no weight is specified then default_match_weight will be taken from config/searchable.php.
Here's an example model including the HasSearch trait:
<?php namespace App\Models; use Maize\Searchable\HasSearch; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Facades\DB; class Article extends Model { use HasSearch; protected $fillable = [ 'id', 'title', 'body', 'creator_name', 'creator_surname', ]; protected $casts = [ 'body' => 'array', ]; /** * Get the model's searchable attributes. * * @return array */ public function getSearchableAttributes(): array { return [ 'title' => 5, // Model attribute 'body.en' => 2, // Single json key of a model attribute 'tags.name', // Relationship attribute 'tags.description.*', // All json keys of a relationship attribute DB::raw("CONCAT(creator_name, ' ', creator_surname)"), // Raw expressions are supported too ]; } /** * Allows fetching the tags bound to current article instance * * @return BelongsToMany */ public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class)->withTimestamps(); } }
Now you can just search for a given term using the scopeSearch scope method:
use App\Models\Article; $searchTerm = 'the search string'; Article::query() ->search($searchTerm) ->where('column', '=', 'something') ->get();
That's all!
The package generates an SQL query with an 'or' condition for each search term and each searchable fields. The given query returns all models matching the search terms. Furthermore, search results are weighted, which means the query will be ordered by the most matching models.
If you don't want to order the search results by its match weight, you can set the orderByWeight flag to false:
use App\Models\Article; $searchTerm = 'the search string'; Article::query() ->search($searchTerm, false) ->where('column', '=', 'something') ->get();
SQLite support
Plain columns, relationship attributes, raw expressions, weights and json attributes are all supported on SQLite, with the following requirements and differences:
- JSON functions: json attributes rely on the
json_extractandjson_eachfunctions, which are built into SQLite since version 3.38 (older versions must be compiled with the JSON1 extension). The searched column must contain valid json orNULL, otherwise SQLite raises amalformed JSONerror. - Json wildcard (
attribute.*): the search is applied to the top level values of the json object only. Json keys are never matched. Nested objects and arrays are searched in their raw json form, where non-ASCII characters may be stored as escaped sequences (e.g.\u00e0) and are therefore not matched. - Case sensitivity: SQLite's
LOWERfunction andLIKEoperator only fold ASCII characters. Searches are case insensitive for ASCII characters (APPLEmatchesapple), and lowercase non-ASCII characters are matched as is (bothcittĂandCITTĂ€match a storedCittĂ). A stored uppercase non-ASCII character is never matched by its own letter though (neitherĂ bcnorĂ€BCmatch a storedĂ€BC). - Accent sensitivity: searches are accent sensitive (
cittadoes not matchCittĂ). - Whole json columns: when a json column is searched as a plain attribute (
bodyinstead ofbody.enorbody.*) the raw json text is searched, so escaped non-ASCII characters are not matched. Use a json key or the wildcard selector instead. - Raw expressions: expressions are sent to the database as they are, so they must be valid SQLite. As an example, the
CONCATfunction is only available since SQLite 3.44: use the||operator on older versions. - Amount of results: the keys of all matching models are bound to the final query, which is limited by SQLite's maximum number of bound parameters (32766 by default since SQLite 3.32).
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.