ralphschindler/basic-query-filter
A lightweight Laravel package for applying request-driven filters to Eloquent queries. Map query-string parameters to allowed columns and operators, keeping filtering logic out of controllers and list endpoints predictable and reusable.
Install via Composer (expect to need --ignore-platform-reqs on modern PHP; the last release was 2017):
composer require ralphschindler/basic-query-filter
First, inspect vendor/ralphschindler/basic-query-filter/src/ and tests/ to learn the actual entry point. The package is a parser only — it does not execute filters against a database. Look for a Parser, Lexer, or Filter class. A typical first use case is parsing a request query string such as:
status = 'active' AND created_at > '2024-01-01'
into a token tree/AST that you then translate into Eloquent where clauses. Start with a tiny script or Artisan tinker session:
$parser = new \BasicQueryFilter\Parser(); // confirm class name from src/
$ast = $parser->parse("status = 'active' AND age > 18");
dump($ast);
Once you can dump the AST, write a small evaluator that maps its nodes to Laravel query builder calls. Do not try to use the package directly as a query builder — it is a language parser, not an ORM integration.
Wrap parsing and application in a dedicated service. Keep the parser isolated from Eloquent so you can test grammar separately from query generation.
namespace App\Filters;
use BasicQueryFilter\Parser; // adjust namespace after inspecting package
class QueryFilter
{
public function __construct(private Parser $parser) {}
public function apply($query, string $filter): void
{
if (trim($filter) === '') {
return;
}
$ast = $this->parser->parse($filter);
$query->where(function ($q) use ($ast) {
$this->applyNode($q, $ast);
});
}
private function applyNode($query, $node): void
{
// Map the package's AST node types to where/orWhere/etc.
// Example shape depends on the package — inspect with dd() first.
}
}
Whitelist columns and operators. Maintain arrays like $allowedColumns = ['status', 'created_at', 'age'] and $allowedOperators = ['=', '!=', '>', '<', '>=', '<=', 'LIKE']. Reject anything else with a 400 response.
Use in a controller or form request.
public function index(Request $request, QueryFilter $filter)
{
$query = User::query();
try {
$filter->apply($query, $request->query('filter', ''));
} catch (\Throwable $e) {
abort(400, 'Invalid filter syntax.');
}
return $query->paginate();
}
Prefer a query scope for reuse.
public function scopeFiltered($query, string $filter)
{
app(QueryFilter::class)->apply($query, $filter);
return $query;
}
Handle nested AND/OR recursively. If the parser returns a binary tree, write applyNode() to detect logical nodes and recursively call where() or orWhere() with closures. For grouped expressions, pass a closure to where() so Laravel adds parentheses correctly.
Keep value binding safe. Always pass values as bindings, never concatenate into whereRaw():
$query->where($column, $operator, $value); // safe
Test parser output separately from SQL. Use PHPUnit to assert the AST shape for representative filters, then feature-test endpoints with assertDatabaseHas or by inspecting toSql() and getBindings().
--ignore-platform-reqs or fork the package and relax composer.json constraints.tests/. The supported syntax may be narrower than you expect — for example, quoted strings with spaces, escaped quotes, case-insensitive operators, or IN (...) may not parse. Validate against your real filter strings before committing to the package.orderBy, select, or whereRaw. Whitelist columns and operators. Treat the filter string as untrusted input even though the parser is doing the parsing.dd() or var_dump() in a route closure during development. After building the query, inspect ->toSql() and ->getBindings() to confirm parentheses and parameter order.app/ is often more practical than waiting for upstream changes.composer.json and document the fork/upgrade path for your team.How can I help you explore Laravel packages today?