{"id":37002741,"url":"https://github.com/bayareawebpro/searchable-resource","last_synced_at":"2026-01-14T00:30:14.796Z","repository":{"id":44544980,"uuid":"240904361","full_name":"bayareawebpro/searchable-resource","owner":"bayareawebpro","description":"Searchable Resource Builder","archived":true,"fork":false,"pushed_at":"2022-02-09T00:28:15.000Z","size":177,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-27T14:40:45.227Z","etag":null,"topics":["api","builder","laravel","resource","searchable","spa","vue"],"latest_commit_sha":null,"homepage":null,"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/bayareawebpro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-16T14:21:06.000Z","updated_at":"2024-05-08T10:46:51.000Z","dependencies_parsed_at":"2022-09-03T22:01:21.514Z","dependency_job_id":null,"html_url":"https://github.com/bayareawebpro/searchable-resource","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/bayareawebpro/searchable-resource","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Fsearchable-resource","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Fsearchable-resource/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Fsearchable-resource/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Fsearchable-resource/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bayareawebpro","download_url":"https://codeload.github.com/bayareawebpro/searchable-resource/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Fsearchable-resource/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28406493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T21:51:37.118Z","status":"ssl_error","status_checked_at":"2026-01-13T21:45:14.585Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["api","builder","laravel","resource","searchable","spa","vue"],"created_at":"2026-01-14T00:30:13.797Z","updated_at":"2026-01-14T00:30:14.763Z","avatar_url":"https://github.com/bayareawebpro.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel Searchable Resource Builder\n\n![CI](https://github.com/bayareawebpro/searchable-resource/workflows/ci/badge.svg)\n![Coverage](https://codecov.io/gh/bayareawebpro/searchable-resource/branch/master/graph/badge.svg)\n![Downloads](https://img.shields.io/packagist/dt/bayareawebpro/searchable-resource.svg)\n![Version](https://img.shields.io/github/v/release/bayareawebpro/searchable-resource.svg)\n![MIT](https://img.shields.io/badge/License-MIT-success.svg)\n\nSearchable Resource Builder is an abstraction for building \nsearchable resource responses in Laravel applications. Extract \nquery logic into reusable chunks while using a fluent builder \ninterface for dealing with searchable / filterable / sortable \nrequests and JSON / API Resources.\n\n```bash\ncomposer require bayareawebpro/searchable-resource\n```\n\n### Basic Usage\n\nSearchableResources implement the `Responsable` interface which allows them to be \nreturned from controllers easily. It can also be used with blade.\n\nThe ```make``` method accepts instances of Eloquent Builder.  \n\n```php\nSearchableResource::make(User::query());\n```\n\t\n### Ordering and Sorting\n\nYou can specify as many orderable columns as you wish.\n\n```php\nSearchableResource::make(User::query())\n\t-\u003eorderable(['name', 'email'])\n\t-\u003eorderBy('name')\n\t-\u003esort('desc')\n\t-\u003epaginate(16);\n```\n\nThe default settings:\n\n* order_by ID\n* sort DESC\n\n---\n\n### Full Example\n\n```php\nuse App\\User;\nuse App\\Queries\\UserSearch;\nuse App\\Queries\\RoleFilter;\nuse App\\Http\\Resources\\UserResource;\nuse BayAreaWebPro\\SearchableResource\\SearchableResource;\nuse BayAreaWebPro\\SearchableResource\\SearchableBuilder;\n\nSearchableResource::make(User::query())\n    -\u003eresource(UserResource::class)\n    -\u003equeries([\n        UserSearch::class,\n        RoleFilter::class\n    ])\n    -\u003eorderable([\n        'id', 'name', 'email', 'role',\n        'created_at', 'updated_at',\n    ])\n    -\u003eappendable([\n        'created_for_humans',\n        'updated_for_humans',\n    ])\n    -\u003eselect([\n        'id',\n        'name', 'email', 'role',\n        'created_at', 'updated_at',\n    ])\n    -\u003erules([\n       'my_filter_key' =\u003e 'required|string'\n    ])\n    -\u003eparams([\n       'my_filter_key' =\u003e 'my_default'\n    ])\n    -\u003eoptions([\n       'my_filter_key' =\u003e ['my_default', 'option2', 'option3']\n    ])\n    -\u003ewith([\n        'my_key' =\u003e true\n    ])\n    -\u003ewhen(true, fn(SearchableBuilder $builder)=\u003e$builder\n        -\u003ewith([\n            'my_key' =\u003e false\n        ])\n    )\n    -\u003eorderBy('updated_at')\n    -\u003esort('desc')\n    -\u003epaginate(16)\n    -\u003elabeled();\n```\n---\n\n### Blade / View Example\n\nExecute the query and return a view with the items and options.\n\n```php\npublic function index()\n{\n    $resource = SearchableResource::make(User::query())\n        -\u003equery(Users::make())\n        -\u003eorderable(['name', 'email'])\n        -\u003eorderBy('name')\n        -\u003esort('desc')\n        -\u003epaginate(5)\n        -\u003eexecute()\n    ;\n    return view('users.index', [\n        'items' =\u003e$resource-\u003egetItems(),\n        'search' =\u003e$resource-\u003egetSearch(),\n        'order_by' =\u003e$resource-\u003egetOrderBy(),\n        'per_page' =\u003e$resource-\u003egetPerPage(),\n        'options' =\u003e$resource-\u003egetOptions(),\n        'sort' =\u003e$resource-\u003egetSort(),\n    ]);\n}\n```\n\n```html\n\u003cx-admin::form method=\"GET\"\u003e\n    \u003cx-admin::search\n        name=\"search\"\n        value=\"{{ $search ?? null }}\"\n    /\u003e\n    \u003cx-admin::select\n        name=\"order_by\"\n        value=\"{{ $order_by ?? null }}\"\n        :options=\"$options-\u003eget('order_by')\"\n    /\u003e\n    \u003cx-admin::select\n        name=\"sort\"\n        value=\"{{ $sort ?? null }}\"\n        :options=\"$options-\u003eget('sort')\"\n    /\u003e\n    \u003cx-admin::select\n        name=\"per_page\"\n        value=\"{{ $per_page ?? null }}\"\n        :options=\"$options-\u003eget('per_page')\"\n    /\u003e\n    \u003cx-admin::submit\n        label=\"{{ __('resources.filter') }}\"\n    /\u003e\n\u003c/x-admin::form\u003e\n```\n\n---\n\n### JSON Resources\n\nSearchableResources are generic JsonResources by default.  You can easily specify \nwhich resource class should be used to map your models when building the response.\n\n\u003e Must extend `JsonResource`.\n\n```php\nSearchableResource::make(User::query())-\u003eresource(UserResource::class);\n```\n\n---\n\n### Invokable Queries\n\nQueries are expressed as invokable classes that extend the `AbstractQuery` class \nwhich contains logic per request field.  Queries can apply to multiple attributes/columns\nas well as multiple inputs for `orWhere` clauses.  \n\n`php artisan make:searchable NameQuery`\n\nThe following is an example of a generic name query:  \n\n```php\n\u003c?php declare(strict_types=1);\n \nnamespace App\\Queries;\n \nuse Illuminate\\Database\\Eloquent\\Builder;\nuse BayAreaWebPro\\SearchableResource\\AbstractQuery;\n \nclass LikeQuery extends AbstractQuery\n{\n    public string $field = 'search';\n    protected string $attribute = 'name';\n    public function __invoke(Builder $builder): void\n    {\n        $builder-\u003ewhere($this-\u003eattribute, \"like\", \"%{$this-\u003egetValue($this-\u003efield)}%\");\n    }\n}\n```\n```php\nSearchableResource::make(User::query())\n    -\u003equery(\n        LikeQuery::make()\n            -\u003efield('search')\n            -\u003eattribute('last_name')\n    )\n    -\u003equery(\n        SelectQuery::make()\n            -\u003efield('role')\n            -\u003eattribute('role')\n            -\u003eoptions(['admin', 'customer'])\n     )\n;\n```\n\n### ConditionalQuery Contract\n\nQueries that implement the `ConditionalQuery` Contract will only be applied when \ntheir `applies` method returns `true`. \n \nBy default an query that extends `AbstractQuery` class using the `ConditionalQuery` contract \nalready implements this method for you by calling the `filled` method on the request.  \nOverride the parent method to customize.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace App\\Queries;\n \nuse BayAreaWebPro\\SearchableResource\\AbstractQuery;\nuse BayAreaWebPro\\SearchableResource\\Contracts\\ConditionalQuery;\n \nclass ConditionalRoleQuery extends AbstractQuery implements ConditionalQuery\n{\n    public string $field = 'role';\n    protected string $attribute = 'role';\n \n    public function __invoke(Builder $builder): void\n    {\n        $builder-\u003ewhere($this-\u003eattribute, $this-\u003egetValue($this-\u003efield));\n    }\n\n    public function getApplies(): bool\n    {\n    \treturn parent::getApplies(); // Customize with $this-\u003erequest\n    }\n}\n```\n\n---\n\n### Validation\n\nQueries can specify their own validation rules by implementing the `ValidatableQuery` \ncontract to be merged the rules for the searchable collection.\n\n### ValidatableQuery Contract\n\nQueries that implement the `ValidatableQuery` Contract will have their returned rules \nmerged into the validator, otherwise the rules will be ignored.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace App\\Queries;\n \nuse BayAreaWebPro\\SearchableResource\\AbstractQuery;\nuse BayAreaWebPro\\SearchableResource\\Contracts\\ValidatableQuery;\n \nclass ConditionalRoleQuery extends AbstractQuery implements ValidatableQuery\n{\n\n    public string $role = 'role';\n    public string $admins = 'only_admins';\n    \n    protected string $attribute = 'role';\n \n    public function __invoke(Builder $builder): void\n    {\n        $builder-\u003ewhere($this-\u003eattribute, $this-\u003egetValue($this-\u003eadmins) ?: $this-\u003egetValue($this-\u003erole));\n    }\n\n    public function getRules(): array\n    {\n        return [\n           $this-\u003erole =\u003e [\n               'required'\n           ],\n           $this-\u003eadmins =\u003e [\n               'sometimes'\n           ],\n        ];\n    }\n}\n```\n\n### ProvidesOptions Contract\n\nQueries can provide options that will be appended to the request options \ndata by implementing the `ProvidesOptions` contract.  This method should return \na flat array of values that are injected into the response query options data.\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace App\\Queries;\n \nuse BayAreaWebPro\\SearchableResource\\AbstractQuery;\nuse BayAreaWebPro\\SearchableResource\\Contracts\\ProvidesOptions;\n \nclass ProvidesOptionsQuery extends AbstractQuery implements ProvidesOptions\n{\n\n    public string $field = 'role';\n    protected string $attribute = 'role';\n \n    public function __invoke(Builder $builder): void\n    {\n        $builder-\u003ewhere($this-\u003eattribute, $this-\u003egetValue($this-\u003efield));\n    }\n\n    public function getOptions(): array\n    {\n        return [\n            $this-\u003efield =\u003e [\n                'admin', 'editor'\n            ],\n        ];\n    }\n}\n```\n\n### Options Formatting\n\nOptions can be formatted with labels for usage with forms and filters by calling \nthe `labeled()` method on the builder.  The labeled method accept a boolean \nvalue which can be used to enable when the request has a session.\n\n\u003e You can return preformatted options (label / value array) from queries \n\u003e or use the formatter to generate labeled options.\n\n### API Options Schema\n\nOptions for API requests are typically not-formatted for speed.\n\n```\npublic function getOptions(): array\n{\n    return [\n        'role' =\u003e [\n            'admin',\n            'customer'\n        ]\n    ];\n}\n```\n\n### Blade Options Schema\n\nOptions for Blade requests can be formatted for usability.\n\n```\npublic function getOptions(): array\n{\n    return [\n        $this-\u003efield =\u003e [\n            [\n                'label' =\u003e 'Admin',\n                'value' =\u003e 'admin'\n            ],\n            [\n                'label' =\u003e 'Customer',\n                'value' =\u003e 'customer'\n            ]\n        ]\n    ];\n}\n```\n\n### FormatsOptions Contract\n\nYou can override the default formatter by specifying a formatter instance.\n\n```php\nSearchableResource::make(User::query())-\u003euseFormatter(new OptionsFormatter);\n```\n\n```php\n\u003c?php declare(strict_types=1);\n\nnamespace App\\Http\\Resources\\Formatters;\n\nuse Illuminate\\Support\\Collection;\nuse BayAreaWebPro\\SearchableResource\\OptionsFormatter as Formatter;\n\nclass OptionsFormatter extends Formatter {\n\n    /**\n     * @param string $key\n     * @param Collection $options\n     * @return Collection\n     */\n    public function __invoke(string $key, Collection $options): Collection\n    {\n        if($key === 'abilities'){\n            return $this-\u003enullable($this-\u003eliteral($options));\n        }\n        if($key === 'role'){\n            return $this-\u003enullable($this-\u003etitleCase($options));\n        }\n        return $this-\u003ebaseOptions($key, $options);\n    }\n}\n```\n\n### Setting Up Default Options\n\nYou can setup a resolving callback in a service provider to pre-bind options to every instance.\n\n```php\n\nuse BayAreaWebPro\\SearchableResource\\OptionsFormatter;\nuse BayAreaWebPro\\SearchableResource\\SearchableBuilder;\n\n$this-\u003eapp-\u003eresolving(\n    SearchableBuilder::class,\n    function (SearchableBuilder $builder){\n    return $builder\n        -\u003euseFormatter(new OptionsFormatter)\n        -\u003elabeled(request()-\u003ehasSession())\n        -\u003eorderBy('created_at')\n        -\u003epaginate(8)\n        -\u003esort('desc')\n    ;\n});\n```\n---\n\n### Adding Queries:\n\nQueries can be added two ways.  First by referencing the class string for easy bulk usage.\n\n```php\nuse App\\Queries\\RoleQuery;\n\nSearchableResource::make(User::query())\n\t-\u003equeries([\n\t\tRoleQuery::class\n\t]);\n```\n\nSecond by instantiating each query using the `make` method.  This can be useful when you need \nmore methods and logic to determine usage. \n\n```php\n\nuse App\\Queries\\RoleQuery;\n\nSearchableResource::make(User::query())\n\t-\u003equery(RoleQuery::make());\n```\n\n---\n\n### Appendable Data\n\nAttributes and fields can be appended to the response by using the following methods: \n\n**For model attributes:** \n\n```php\nSearchableResource::make(User::query())\n    -\u003eappendable([\n        'created_for_humans',\n        'updated_for_humans',\n        'bytes_for_humans',\n    ]);\n```\n\n**For additional data (appended to the response):** \n\n```php\nSearchableResource::make(User::query())\n    -\u003ewith([\n        'my_key' =\u003e []\n    ]);\n```\n\n```json\n{\n    \"my_key\": [],\n    \"data\": []\n}\n```\n\n**For request fields (appended to the query in response):** \n\n```php\nSearchableResource::make(User::query())\n    -\u003efields([\n        'my_filter_state'\n    ]);\n```\n\n```json\n{\n    \"query\": {\n        \"my_filter_state\": true\n    }\n}\n```\n\n---\n\n### When Condition Callback\n\nYou can use a callback or invokable class for more control with less method chaining.\n\n```php\n\nclass SessionEnabledQuery{\n    public function __invoke(SearchableBuilder $builder): void \n    {\n        $builder-\u003elabeled();\n    }\n}\n\nSearchableResource::make(User::query())\n    -\u003ewhen(request()-\u003ehasSession(), new SessionEnabledQuery)\n    -\u003ewhen(request()-\u003ehasSession(), function(SearchableBuilder $builder){\n        $builder-\u003elabeled();\n    })\n;\n```\n---\n\n### Tap Callback\n\nUseful for configuring the builder via an invokable class.\n\n```php\n\nuse BayAreaWebPro\\SearchableResource\\SearchableBuilder;\nuse BayAreaWebPro\\SearchableResource\\Contracts\\InvokableBuilder;\nclass UserSearchable implements InvokableBuilder{\n    public function __invoke(SearchableBuilder $builder): void \n    {\n        $builder-\u003equeries([\n            RoleQuery::class\n        ]);\n    }\n}\n\nSearchableResource::make(User::query())-\u003etap(new UserSearchable);\n```\n\n---\n\n### Response Output\n\nThe relevant query parameters and request options are appended to the output for \nconvenience.  Two additional properties have been added to the pagination parameters \nto remove the need for conditionals on the client / user side `isFirstPage` and `isLastPage` \nmaking pagination buttons easy to disable via props (Vue | React).\n\n\u003e Note: If the `pagination` method is not used, all pagination related properties \n\u003e will be filtered from the output data.\n\n```\n\"data\": [\n    //\n],\n\"pagination\": {\n    \"isFirstPage\": true,\n    \"isLastPage\": true,\n    ...default pagination props...\n},\n\"query\": {\n    \"page\": 1,\n    \"sort\": \"desc\",\n    \"order_by\": \"id\",\t\n    \"search\": \"term\",\n    \"per_page\": 4,\t\n},\n\"options\": {\n    \"orderable\": [\n        \"id\", \n        \"name\"\n    ],\n    \"sort\": [\n        \"asc\"\n        \"desc\"\n    ]\n}\n```\n\n---\n\n### Testing\n\n``` bash\ncomposer test\ncomposer lint\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbayareawebpro%2Fsearchable-resource","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbayareawebpro%2Fsearchable-resource","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbayareawebpro%2Fsearchable-resource/lists"}