{"id":15529339,"url":"https://github.com/ajcastro/searchable","last_synced_at":"2026-03-03T07:32:51.620Z","repository":{"id":48458645,"uuid":"172181633","full_name":"ajcastro/searchable","owner":"ajcastro","description":"Pattern-matching search and reusable queries in laravel.","archived":false,"fork":false,"pushed_at":"2021-07-27T08:10:07.000Z","size":184,"stargazers_count":28,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-16T02:03:17.155Z","etag":null,"topics":["eloquent-search","laravel","laravel-package","query","query-builder","searchable"],"latest_commit_sha":null,"homepage":"https://ajcastro.github.io/searchable","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ajcastro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-23T06:46:14.000Z","updated_at":"2023-12-08T15:00:44.000Z","dependencies_parsed_at":"2022-08-21T07:50:36.308Z","dependency_job_id":null,"html_url":"https://github.com/ajcastro/searchable","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajcastro%2Fsearchable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajcastro%2Fsearchable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajcastro%2Fsearchable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajcastro%2Fsearchable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ajcastro","download_url":"https://codeload.github.com/ajcastro/searchable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250436793,"owners_count":21430555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eloquent-search","laravel","laravel-package","query","query-builder","searchable"],"created_at":"2024-10-02T11:17:20.899Z","updated_at":"2026-03-03T07:32:46.588Z","avatar_url":"https://github.com/ajcastro.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Searchable\n\nPattern-matching search for Laravel eloquent models.\n\n- Currently supports MySQL only.\n- Helpful for complex table queries with multiple joins and derived columns.\n- Fluent columns definitions.\n\n## Demo Project\n\nSee [demo project](https://github.com/ajcastro/searchable-demo).\n\n## Overview\n\nSimple setup for searchable model and can search on derived columns.\n\n```php\nuse AjCastro\\Searchable\\Searchable;\n\nclass Post\n{\n    use Searchable;\n\n    protected $searchable = [\n        // This will search on the defined searchable columns\n        'columns' =\u003e [\n            'posts.title',\n            'posts.body',\n            'author_full_name' =\u003e 'CONCAT(authors.first_name, \" \", authors.last_name)'\n        ],\n        'joins' =\u003e [\n            'authors' =\u003e ['authors.id', 'posts.author_id']\n        ]\n    ];\n\n    public function author()\n    {\n        return $this-\u003ebelongsTo(Author::class);\n    }\n}\n\n// Usage\nPost::search(\"Some title or body content or even the author's full name\")\n    -\u003ewith('author')\n    -\u003epaginate();\n```\n\nImagine we have an api for a table or list that has searching and column sorting and pagination.\nThis is a usual setup for a table or list. The internal explanations will be available on the documentation below.\nOur api call may look like this:\n\n`\nhttp://awesome-app.com/api/posts?per_page=10\u0026page=1\u0026sort_by=title\u0026descending=true\u0026search=SomePostTitle\n`\n\nYour code can look like this:\n\n```php\nclass PostsController\n{\n    public function index(Request $request)\n    {\n        $query = Post::query();\n\n        return $query\n            -\u003ewith('author')\n            // advance usage with custom search string parsing\n            -\u003ewhen($request-\u003eparse_using === 'exact', function ($query) {\n                $query-\u003eparseUsing(function ($searchStr) {\n                    return \"%{$searchStr}%\";\n                });\n            })\n            -\u003esearch($request-\u003esearch)\n            -\u003ewhen(\n                $request-\u003ehas('sort_by') \u0026\u0026 $query-\u003egetModel()-\u003eisColumnValid($request-\u003esort_by),\n                function ($query) use ($request) {\n                    $query-\u003eorderBy(\n                        DB::raw($query-\u003egetModel()-\u003egetColumn($request-\u003esort_by)),\n                        $request-\u003edescending ? 'desc' : 'asc'\n                    );\n                },\n                function ($query) {\n                    $query-\u003esortByRelevance();\n                },\n            )\n            -\u003epaginate();\n    }\n\n}\n```\n\n## Documentation\n\n### Installation\n\n```\ncomposer require ajcastro/searchable\n```\n\n### Searchable Model\n\n```php\nuse AjCastro\\Searchable\\Searchable;\n\nclass Post extends Model\n{\n    use Searchable;\n\n    /**\n     * Searchable model definitions.\n     */\n     protected $searchable = [\n        // Searchable columns of the model.\n        // If this is not defined it will default to all table columns.\n        'columns' =\u003e [\n            'posts.title',\n            'posts.body',\n            'author_full_name' =\u003e 'CONCAT(authors.first_name, \" \", authors.last_name)'\n        ],\n        // This is needed if there is a need to join other tables for derived columns.\n        'joins' =\u003e [\n            'authors' =\u003e ['authors.id', 'posts.author_id'], // defaults to leftJoin method of eloquent builder\n            'another_table' =\u003e ['another_table.id', 'authors.another_table_id', 'join'], // can pass leftJoin, rightJoin, join of eloquent builder.\n        ]\n    ];\n\n    /**\n     * Can also be written like this for searchable columns.\n     *\n     * @var array\n     */\n    protected $searchableColumns = [\n        'title',\n        'body',\n        'author_full_name' =\u003e 'CONCAT(authors.first_name, \" \", authors.last_name)'\n    ];\n\n    /**\n     * Can also be written like this for searchable joins.\n     *\n     * @var array\n     */\n    protected $searchableJoins = [\n        'authors' =\u003e ['authors.id', 'posts.author_id']\n    ];\n}\n\n// Usage\n// Call search anywhere\n// This only search on the defined columns.\nPost::search('Some post')-\u003epaginate();\nPost::where('likes', '\u003e', 100)-\u003esearch('Some post')-\u003epaginate();\n\n```\n\nIf you want to sort by relevance, call method `sortByRelevance()` after `search()` method.\nThis will addSelect field `sort_index` which will be used to order or sort by relevance.\n\nExample:\n\n```\nPost::search('Some post')-\u003esortByRelevance()-\u003epaginate();\nPost::where('likes', '\u003e', 100)-\u003esearch('Some post')-\u003esortByRelevance()-\u003epaginate();\n```\n\n### Set searchable configurations on runtime.\n\n```php\n$post = new Post;\n$post-\u003esetSearchable([ // addSearchable() method is also available\n    'columns' =\u003e [\n        'posts.title',\n        'posts.body',\n    ],\n    'joins' =\u003e [\n        'authors' =\u003e ['authors.id', 'posts.author_id']\n    ]\n]);\n// or\n$post-\u003esetSearchableColumns([ // addSearchableColumns() method is also available\n    'posts.title',\n    'posts.body',\n]);\n$post-\u003esetSearchableJoins([ // addSearchableJoins() method is also available\n    'authors' =\u003e ['authors.id', 'posts.author_id']\n]);\n```\n\n### Easy Sortable Columns\n\nYou can define columns to be only sortable but not be part of search query constraint.\nJust put it under `sortable_columns` as shown below .\nThis column can be easily access to put in `orderBy` of query builder. All searchable columns are also sortable columns.\n\n```php\nclass Post {\n     protected $searchable = [\n        'columns' =\u003e [\n            'title' =\u003e 'posts.title',\n        ],\n        'sortable_columns' =\u003e [\n            'status_name' =\u003e 'statuses.name',\n        ],\n        'joins' =\u003e [\n            'statuses' =\u003e ['statuses.id', 'posts.status_id']\n        ]\n    ];\n}\n\n// Usage\n\nPost::search('A post title')-\u003eorderBy(Post::make()-\u003egetSortableColumn('status_name'));\n// This will only perform search on `posts`.`title` column and it will append \"order by `statuses`.`name`\" in the query.\n// This is beneficial if your column is mapped to a different column name coming from front-end request.\n```\n\n\n### Custom Search String Parser - Exact Search Example\n\nOverride the `deafultSearchQuery` in the model like so:\n\n```php\nuse AjCastro\\Searchable\\BaseSearch;\n\nclass User extends Model\n{\n    public function defaultSearchQuery()\n    {\n        return BaseSearch::make($this-\u003ebuildSearchableColumns())\n            -\u003eparseUsing(function ($searchStr) {\n                return $searchStr; // produces \"where `column` like '{$searchStr}'\"\n                return \"%{$searchStr}%\"; // produces \"where `column` like '%{$searchStr}%'\"\n            });\n    }\n}\n```\n\nYou may also check the build query by dd-ing it:\n\n```php\n$query = User::search('John Doe');\ndd($query-\u003etoSql());\n```\nwhich may output to\n```\nselect * from users where `column` like 'John Doe'\n// or\nselect * from users where `column` like '%John Doe%'\n```\n\n### Using derived columns for order by and where conditions\n\nUsually we have queries that has a derived columns like our example for `Post`'s `author_full_name`.\nSometimes we need to sort our query results by this column.\n\n```php\n$query = Post::query();\n$post = $query-\u003egetModel();\n// (A)\n$query-\u003esearch('Some search')-\u003eorderBy($post-\u003egetColumn('author_full_name'), 'desc')-\u003epaginate();\n// (B)\n$query-\u003esearch('Some search')-\u003ewhere($post-\u003egetColumn('author_full_name'), 'William%')-\u003epaginate();\n```\nwhich may output to\n```sql\n-- (A)\nselect * from posts where ... order by CONCAT(authors.first_name, \" \", authors.last_name) desc limit 0, 15;\n-- (B)\nselect * from posts where ... and CONCAT(authors.first_name, \" \", authors.last_name) like 'William%' limit 0, 15;\n```\n\n## Helper methods available\n\n### TableColumns::get() [static]\n\n- Get the table columns.\n\n```php\nTableColumns::get('posts');\n```\n\n### isColumnValid\n\n- Identifies if the column is a valid column, either a regular table column or derived column.\n- Useful for checking valid columns to avoid sql injection especially in `orderBy` query, [see post](https://freek.dev/1317-an-important-security-release-for-laravel-query-builder).\n\n```php\n$query-\u003egetModel()-\u003eisColumnValid(request('sort_by'));\n```\n\n### enableSearchable\n\n- Enable the searchable behavior.\n\n```php\n$query-\u003egetModel()-\u003eenableSearchable();\n$query-\u003esearch('foo');\n```\n\n### disableSearchable\n\n- Disable the searchable behavior.\n- Calling `search()` method will not perform a search.\n\n```php\n$query-\u003egetModel()-\u003edisableSearchable();\n$query-\u003esearch('foo');\n```\n\n### setSearchable\n\n- Set or override the model's `$searchable` property.\n- Useful for building searchable config on runtime.\n\n```php\n$query-\u003egetModel()-\u003esetSearchable([\n  'columns' =\u003e ['title', 'status'],\n  'joins' =\u003e [...],\n]);\n$query-\u003esearch('foo');\n```\n\n### addSearchable\n\n- Add columns or joins in the model's `$searchable` property.\n- Useful for building searchable config on runtime.\n\n```php\n$query-\u003egetModel()-\u003eaddSearchable([\n  'columns' =\u003e ['title', 'status'],\n  'joins' =\u003e [...],\n]);\n$query-\u003esearch('foo');\n```\n\n## Warning\n\nCalling `select()` after `search()` will overwrite `sort_index` field, so it is recommended to call `select()`\nbefore `search()`. Or you can use `addSelect()` instead.\n\n## Credits\n\n- Ray Anthony Madrona [@raymadrona](https://github.com/raymadrona), for the tips on using MySQL `LOCATE()` for sort relevance.\n- [nicolaslopezj/searchable](https://github.com/nicolaslopezj/searchable), for the `$searchable` property declaration style.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajcastro%2Fsearchable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fajcastro%2Fsearchable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajcastro%2Fsearchable/lists"}