iwastenot/laravel-api-query-helper
Composer 安装命令:
composer require iwastenot/laravel-api-query-helper
包简介
A helper class for building API queries in Laravel
README 文档
README
A structured query parser and Eloquent/Scout integration toolkit for Laravel APIs
The iWasteNot API Query Helper provides a consistent way to transform HTTP query parameters into robust Eloquent (or Laravel Scout) queries.
It standardizes filtering, sorting, field selection, and relationship inclusion for JSON APIs without requiring repetitive controller code.
🧱 Core Concept
Write API endpoints like:
GET /api/items?_with=photos&_fields=id,title,price&_sort=-created_at&price-gte=100&status-eq=active
and get this automatically:
Item::with(['photos'])
->select(['id', 'title', 'price'])
->where('price', '>=', 100)
->where('status', '=', 'active')
->orderBy('created_at', 'desc')
->get();
All query parsing, validation, and recursion are handled internally.
✅ Compatibility
| Component | Supported versions |
|---|---|
| PHP | >= 7.4 |
| Laravel / Illuminate | 8.x, 9.x, 10.x, 11.x, 12.x |
Notes:
- When PHP is 7.4, only Laravel/Illuminate 8.x is supported.
⚡ Quick Start
composer install --no-interaction --prefer-dist
vendor/bin/phpunit
🚀 Installation
composer require iwastenot/laravel-api-query-helper
Then import the helper where you build your API queries:
use IWasteNot\ApiQueryHelper\Services\ApiQueryHelper;
class ItemController extends Controller
{
public function index(Request $request, ApiQueryHelper $api)
{
$query = Item::query();
$results = $api->indexHelper($query, $request->all())
->paginate();
return new IndexCollection($results);
}
}
📚 Documentation
docs/tutorials/getting-started.md— walkthrough from model to requestdocs/how-to/filters.md— filter patterns + suffix listdocs/how-to/relations.md— nested_with,_fields,_sort, filtersdocs/how-to/scout.md— Scout search and_qdocs/ApiQueryHelper.md— reference for helper methodsdocs/ApiModel.md— reference for model helpersdocs/glossary.md— glossary of query termsdocs/faq.md— common questions and troubleshootingdocs/migration-notes.md— behavior changes between releasesdocs/developer-guide.md— contributor setup notes
⚙️ Supported Parameters
| Parameter | Type | Description |
|---|---|---|
_with | comma-separated list | Eager-load relationships (with()) |
_fields | comma-separated list | Restrict selected columns |
_sort | comma-separated list | Sort fields, prefix with - for descending; dotted paths (category-name) sort by related columns |
_q | string | Model-directed search across the model's $searchable fields |
_or[N][...] | bracketed groups | Or-groups: entries or-combine, groups and-combine with everything else |
| all others | filters | Translated into where*() clauses based on suffix |
🔎 Filter Syntax
Filters are derived from all parameters except _with, _fields, _sort,
_q, and _or. Keys that start with _ are ignored by the filter parser.
| Example | Effect | Method |
|---|---|---|
status-eq=active | status = 'active' | where() |
price-gte=100 | price >= 100 | where() |
deleted-null | deleted IS NULL | whereNull() |
deleted-not-null | deleted IS NOT NULL | whereNotNull() |
id-in=1,2,3 | id IN (1,2,3) | whereIn() |
role-not-in=admin,staff | role NOT IN (...) | whereNotIn() |
created-between=2024-01-01,2024-12-31 | Date range | whereBetween() |
gte=price,100 | price >= 100 | where() |
status=active | status = 'active' | where() |
title-lk=%a%|%b% | title LIKE '%a%' OR title LIKE '%b%' | grouped orWhere() |
category-name-lk=%x% | parent rows with a matching category | whereHas() |
_or[0][a-lk]=%x%&_or[0][b-lk]=%y% | (a LIKE '%x%' OR b LIKE '%y%') | nested where() |
Supported suffixes
The helper supports these suffixes by default:
lk,like=>where ... likenot-lk,not-like=>where ... not likegte=>where ... >=lte=>where ... <=gt=>where ... >lt=>where ... <eq=>where ... =not,not-eq=>where ... !=null=>whereNullnot-null=>whereNotNullin=>whereInnot-in=>whereNotInbetween=>whereBetweennot-between=>whereNotBetweendate=>whereDateyear=>whereYearmonth=>whereMonthday=>whereDaytime=>whereTimecolumn=>whereColumn
For null / not-null, the value list is ignored and no parameters are passed
to the where method.
Suffixes and aliases are normalized via a configurable $suffixes map.
In the suffix-as-key form (for example, gte=price,100), the first value is
the field name and the remaining values are parameters for the where clause.
In the suffix-in-key form, hyphens in the field portion are interpreted as dots
for nested relations (for example, photos-filename-like=image targets
photos.filename).
🧮 Eloquent Integration
Models intended for API use should extend the provided ApiModel class.
Example
use IWasteNot\ApiQueryHelper\Models\ApiModel;
class Item extends ApiModel
{
protected $fillable = ['title', 'price', 'status'];
protected $joinable = ['photos', 'tags'];
protected $details = ['description', 'notes'];
public function photos() { return $this->hasMany(Photo::class); }
public function tags() { return $this->belongsToMany(Tag::class); }
}
This gives ApiQueryHelper introspection capabilities through:
getJoinable()— whitelist of relationships allowed in_withgetKeys()— all known safe fieldsgetIndexKeys()— indexable, visible subsetgetIndexAttributes()— filtered attributes for display
💡 Usage Examples
Basic filtering, sorting, and field selection
GET /api/items?_fields=id,title,price&_sort=-created_at&price-lte=100&status-eq=active
Item::select(['id','title','price'])
->where('price', '<=', 100)
->where('status', '=', 'active')
->orderBy('created_at', 'desc')
->get();
Eager loading and nested filtering
GET /api/items?_with=photos,tags&photos.filename-like=image
→ Loads related photos where the filename matches "image".
Nested relation fields and sorts
GET /api/items?_with=photos&photos._fields=id,filename&photos._sort=-created_at
→ Scopes _fields and _sort to the photos relation.
Full-text search with Laravel Scout
$results = $api->indexHelper(Item::class, ['_q' => 'hammer']);
When you call indexHelper() with a model class name, Scout mode is used only
when the params are limited to _q, per_page, and offset. The helper does
not apply pagination itself; those values are for your controller/paginator.
_q searches the fields that your Scout driver indexes for the model. Define
them by overriding toSearchableArray() and reindex after changes.
Custom prefiltering in models
public function indexHelperEloquent($query, $joins, $fields, $sorts, $filters)
{
$filters['status'][] = ['=', 'active'];
return [$query, $joins, $fields, $sorts, $filters];
}
See docs/ApiQueryHelper.md for extension-point details.
Show helper for single models
$item = Item::findOrFail($id);
$item = $api->showHelper($item, request()->all());
→ Loads _with relations without altering the base query.
🧩 Internal Structure
| File | Role |
|---|---|
ApiQueryHelper.php | Main query parser and orchestrator |
Arr.php | Array filtering utilities for dotted keys |
ApiModel.php | Base model with introspection hooks |
IndexCollection.php | (optional) Resource wrapper for paginated results |
🧪 Testing
vendor/bin/phpunit
⚠️ Limitations
- Scout filters are not whitelisted via
getKeys(). - Pagination is controlled by your controller (the helper does not paginate).
🩹 Troubleshooting
- If Scout returns no results, confirm the driver is configured and the model index is built/rebuilt.
- If filters are ignored, ensure the field is present in
getKeys(). - If nested filters do nothing, confirm the parent relation is in
_with.
Changelog
See CHANGELOG.md for release notes and historical changes.
📄 License
MIT. See LICENSE.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-14