ahmedabdo/searchable

package for adding search functionality to laravel models

Installs: 73

Dependents: 0

Suggesters: 0

Security: 0

Stars: 28

Watchers: 2

Forks: 4

Open Issues: 0

pkg:composer/ahmedabdo/searchable

v1.0.10 2023-10-06 19:02 UTC

This package is auto-updated.

Last update: 2025-12-22 17:18:39 UTC


README

Laravel Searchable is package that adds easy customizable searching and filtering ability to Laravel Eloquent Models

Installation

composer require ahmedabdo/searchable

Searching

Search Usage Example

  • Use the Abdo\Searchable\Searchable trait in your model.
  • Define the columns you want to use for searching within the searchable array.
  • Utilize the search scope to perform searches on the selected columns.
// model
use Abdo\Searchable\Searchable;
use Abdo\Searchable\Attributes\SearchAdd;
use Abdo\Searchable\Attributes\SearchColumns;
class User extends Authenticatable
{
    use Searchable;
	
    #[SearchColumns]
    public $searchable = [
	"columns" => [
	    "name",
	    "email",
	    "role.name",
	    "created_at"
	],
	"eager" => [
	    "role"
	]
    ];

    #[SearchAdd("created_at")]
    public function searchByCreatedAtDayName(Builder $q, string $searchWord) {
        $q->orWhereRaw("DAYNAME(created_at) like ?", ["%" . $searchWord . "%"]);
    }
}

// usage 

User::search($searchWord)->get();

Search Columns

To define the search columns for a model, you can utilize a property decorated with the #[SearchColumns] attribute. This property will contain the default columns for searching the model, as well as any relations that need to be eagerly loaded. In case the #[SearchColumns] attribute is not set, the fillable columns will be used as a fallback.

#[SearchColumns]
public $searchable = [
    "columns" => [
        "colame",
	"relation.colname",
        "relation.relation.colname",
    ],
    "eager" => [
        "relation"
    ]
];

Custom Search

If you wish to customize the search query for a specific column, you can create a method with the #[Search("colname")] attribute and craft a custom query specifically for that chosen column.

#[SearchColumns]
public $searchable = [
    "columns" => [
        "time", // stored as Y-m-d H:i:s
    ]
];

#[Search("time")]
public function searchTime(Builder $q, string $searchWord) {
     $q->orWhere("time", "like", "%" . $searchWord . "%")
       ->orWhereRaw("DAYNAME(time) like ?", ["%" . $searchWord . "%"]);
}

In the example above, the "time" column is stored in the database as a datetime value. If you wish to customize the search functionality for this column by adding a new orWhere statement for searching by day name, you can employ the #[SearchAdd("colname")] attribute.

By utilizing this attribute, you can incorporate the desired orWhere statement into the column search query, while still leveraging the existing search query provided by the package.

Note

you may use as many searchAdd methods as you want.

#[SearchAdd("time")]
public function searchTimeByDayName(Builder $q, string $searchWord) {
     $q->orWhereRaw("DAYNAME(time) like ?", ["%" . $searchWord . "%"]);
}

If you have used either #[Search("colname")] or #[SearchAdd("colname")] to customize relation search, the builder instance passed to the custom method will be the builder for the relation model.

public $searchable = [
    "columns" => [
        "patient.name",
    ]
];

#[Search("patient.name")]// or #[SearchAdd("patient.name")]
public function searchPatientName(Builder $q, string $searchWord) {
     // $q is builder instance for Patient model 
     $q->orWhere("name", "like", "%" . $searchWord . "%");
}

Overwrite Default Columns

If you want to override the columns defined in the model, you can pass the columns as a second parameter to the search scope.

User::search($searchWord, ["fname", "lname"])->get();

Search Options

You can add an options array for columns

#[SearchColumns]
public $searchable = [
    "columns" => [
        "name" => ["operator" => "="]
    ]
];

User::search($searchWord,[
    "name" => ["operator" => "=", "useCustom" => false, "useAddCondition" => false] 
])

The column can have three options

option description values default
operator the operator used for searching operation Avaliable Operators likeContains
useCustom if it uses search method that has #[Search(”col”)] true or false true
useAddCondition if it uses search methods that has #[SearchAdd(”col”)] true or false true

Filtering

Filter Usage Example

// model
use Abdo\Searchable\Searchable;
class User extends Authenticatable
{
     use Searchable;

     #[SearchColumns]
     public $searchable = [
        "columns" => [
            "name",
            "email",
            "role.name",
            "created_at"
        ],
        "eager" => [
            "role"
        ]
    ];
}

//request query string :
// ?name=ahmed&email=startsWith|ahmed&role:name=admin&created_at=from|2020-01-01 

// usage 
User::filter()->get();

Filtering Columns

By default, the columns used for filtering will be those defined in the property with the #[SearchColumns] attribute, combined with the columns specified in the fillable array. If you wish to use different columns as the default for filtering, you can define a property with the #[FilterColumns] attribute.

#[FilterColumns]
public $filterable = [
	"name","email", //... 
];

Filter Query String

The query string parameter names should match to the column names. If you are filtering a relation, you can use a colon : as a separator between the relation name and the column name, instead of a dot . The query string should follow to this pattern.

// ?<colname>=<operator, default:"=">|<value>

User::filter()->get();

If you have used a parameter name that differs from the column name, you can pass both the column name and the filter value to the filter scope.

// ?<not-colname>=<operator, default:"=">|<value>
// To retrieve the filter value from the query string, you can utilize the "filterParam" helper

User::filter(["column_name" => filterParam("not_column_name")])->get();

// Note that if the rest of the query string parameter names match the column names
// you can employ a similar approach.
User::filter(["column_name" => filterParam("not_column_name")])->filter()->get(); 

Operators

The operators allowed for filtering include any operator that can be sent to the where builder method, plus additional operators.

Operator Description Example
“contains” or “cont” filtering out entries where the column value contains the given value ?name=cont|ahmed
“startsWith” or “sw” filtering out entries where the column value starts with the given value ?name=sw|ahmed
“endsWith” or “ew” filtering out entries where the column value ends with the given value ?name=ew|abdo
“In” filtering out entries where the column value exists in the given values ?role_id=in|1,2,3,4,5
“notIn” filtering out entries where the column value dosen’t exists in the given values ?role_id=notIn|1,5
“is_null” filtering out entries where the column value is null ?role_id=is_null|
“is_not_null” filtering out entries where the column value is not null ?role_id=is_not_null|
“from” filtering out entries where the date column value is after the given value ?created_at=from|2010-01-01
“from_eq” filtering out entries where the date column value is after or equal the given value ?created_at=from_eq|2010-01-01
“to” filtering out entries where the date column value is before the given value ?created_at=to|2010-01-01
“to_eq” filtering out entries where the date column value is before or equal the given value ?created_at=to_eq|2010-01-01
“from_time” same as “from” but for time ?time=from_time|12:30
“from_time_eq” same as “from_eq” but for time ?time=from_time_eq|12:30
“to_time” same as “to” but for time ?time=to_time|12:30
“to_time_eq” same as “to_eq” but for time ?time=to_time_eq|12:30
“between” or “bt” filtering out entries where column value lies in the given range ?created_at=bt|2010-01-01,2015-01-01&role_id=bt|3,5
“betweenEqual” or “bte” filtering out entries where column value lies in given range with boundry ?created_at=bte|2010-01-01,2015-01-01&role_id=bte|3,5
“where” statement operators any operator used in “where” method can be used as filter operator ?age=<|20&gender=male

Note

When using the "between" operator, two arguments must be provided, separated by a comma. If the from argument is not specified, the data will be filtered from negative infinity up to the to argument. Similarly, if the to argument is not provided, the data will be filtered up to positive infinity. For example: ?created_at=bt|,2010-01-01 retrieves all records created before January 1, 2010. Another example: ?created_at=bt|2010-01-01, retrieves all records created after January 1, 2010.

Filter Modes

there is two filter modes and mode and or mode the default mode is and
for changing the mode

    // using or mode
    User::filter(mode: Mode::OR)->get();

    // using and mode
    User::filter(mode: Mode::AND)->get();
    User::filter()->get();

Filter Blade Script

If you are utilizing regular Blade templates for the frontend, there is a simple JavaScript script available that simplifies the creation of filter forms. To include this script, add the @searchableScripts directive to your main layout.

then you can create filter form like by following steps

Usage:

  • ensure that the form has class filter
  • The input name should match the corresponding column name
  • For relations, use a colon : as a separator ex: relation:columnName
  • Set the desired filtering operator in the data-filter attribute, with the default being =
  • use filterValue("queryParam") to get the filter value
<!-- give the form class  'filter' -->
<form action="" class="filter">
    <label for="">Age</label>
    <!-- place the filter operator in 'data-filter' attribute  -->
    <!-- filterValue helper for getting the current filter value  -->
    <input data-filter=">" type="number" value="{{filterValue('age')}}" name="age" id="">
    
    <label for="">Created At From</label>
    <!-- for bte, bt, betweenEqual, between operators  -->
    <!-- ex: from input with name created_at[0] to input with name created_at[1]  -->
    <input data-filter="bt" type="date" value="{{filterValue('created_at', asArray: true)[0] ?? now()->format('Y-m-d')}}" name="created_at[0]" id="">

    <label for="">Created At To</label>
    <input type="date" value="{{filterValue('created_at', asArray: true)[1] ?? now()->format('Y-m-d')}}" name="created_at[1]" id="">
			
    <!-- ex: for multi value   -->
    <label for="">Role</label>
    <select data-filter="in"  name="role_id[]" id="" multiple>
        <option value="0">admin</option>
        <option value="1">manager</option>
        <option value="2">employee</option>
    </select>

    <button type="submit" >filter</button>
</form>

Filter Helpers

filterParam

The "filterParam" helper function is utilized to extract the filter value from the query string. This helper function should be utilized when employing a blade filter form, especially if the input field name differs from the corresponding column name.

//model
#[SearchColumns]
public $searchable = [
    "columns" => [
        "name"
    ],
];

//blade
<form action="" class="filter">
    <input data-filter="contains" type="text" value="{{filterValue('empName')}}" name="empName" id="">
</form>

//usage
User::filter(["name" => filterParam("empName")])->get();

filterValue

The "filterValue" helper is employed to retrieve the value utilized for filtering in order to display this value in the input field, as an illustrative example.

//?name=cont|ahmed&role_id=in|2,3,4

filterValue("name") // ouptut ahmed
filterValue("role_id") // output 2,3,4
filterValue("role_id", true) //output [2,3,4]

Custom Operators

In case you need to define a custom operator for filtering and searching, there are two distinct approaches available.

Note

custom operator must start with sp_

In Config
To publish the configuration file, execute the following command

php artisan vendor:publish --provider="Abdo\Searchable\ServiceProvider"

Once the configuration file has been published, you have the option to define your custom operators within the operators array.

    "operators" => [

        "sp_is_null" => function (Builder $builder, string $column, string $word) {
            return $builder->whereIsNull($column)->orWhere($column, 0);
        },
    ],

In Service Provider

Additionally, you can register your custom operators within the boot method of one of the service providers.

    ColumnConfigraution::registerOperator("sp_is_null", function (Builder $builder, string $column, string $word) {
        return $builder->whereIsNull($column)->orWhere($column, 0);
    });

    //after defining your custom operator you can use them like this
    //?status=sp_is_null|