Since this is a Symfony bundle, we'll adapt it for Laravel using Laravel Doctrine (e.g., laravel-doctrine/orm) or a Symfony bridge like symfony/console-bridge. For simplicity, assume you're using Doctrine ORM in Laravel.
Install Dependencies
composer require artprima/query-filter-bundle
composer require doctrine/orm doctrine/persistence symfony/dependency-injection symfony/config
Register the Bundle (if using Symfony Bridge)
In config/app.php, add the bundle to the config.bundles array (if using a Symfony bridge):
Artprima\QueryFilterBundle\ArtprimaQueryFilterBundle::class => ['all' => true],
Basic Controller Usage Create a controller to handle filtered queries:
use Artprima\QueryFilterBundle\QueryFilter\Config\BaseConfig;
use Artprima\QueryFilterBundle\Request\Request;
use Artprima\QueryFilterBundle\QueryFilter\QueryFilter;
use Artprima\QueryFilterBundle\Response\Response;
class ItemController extends Controller
{
public function index(Request $request, ItemRepository $repository)
{
$config = new BaseConfig();
$config->setSearchAllowedCols(['t.name']);
$config->setAllowedLimits([10, 25, 50]);
$config->setDefaultLimit(10);
$config->setSortCols(['t.id'], ['t.id' => 'asc']);
$config->setRequest(new Request($request));
$config->setStrictColumns(true);
$config->setRepositoryCallback([$repository, 'findByOrderBy']);
$queryFilter = new QueryFilter(Response::class);
$response = $queryFilter->getData($config);
$data = $response->getData();
$meta = $response->getMeta();
return response()->json(['data' => $data, 'meta' => $meta]);
}
}
Repository Setup
Extend your repository to work with QueryFilterArgs:
use Artprima\QueryFilterBundle\Query\ConditionManager;
use Artprima\QueryFilterBundle\QueryFilter\QueryFilterArgs;
use Artprima\QueryFilterBundle\QueryFilter\QueryResult;
use Doctrine\ORM\Tools\Pagination\Paginator;
class ItemRepository
{
private $pqbManager;
public function __construct(ConditionManager $manager)
{
$this->pqbManager = $manager;
}
public function findByOrderBy(QueryFilterArgs $args): QueryResult
{
$qb = $this->createQueryBuilder('t')
->setFirstResult($args->getOffset())
->setMaxResults($args->getLimit());
$proxyQb = $this->pqbManager->wrapQueryBuilder($qb);
$qb = $proxyQb->getSortedAndFilteredQueryBuilder($args->getSearchBy(), $args->getSortBy());
$query = $qb->getQuery();
$paginator = new Paginator($query);
return new QueryResult($paginator->getIterator()->getArrayCopy(), count($paginator));
}
}
First API Call Test with a filtered request:
GET /items?filter[t.name]=Doe&limit=25
This will return results where t.name LIKE '%Doe%'.
ItemConfig) to avoid repeating logic in controllers.
class ItemConfig extends BaseConfig
{
public function __construct()
{
$this->setSearchAllowedCols(['t.name', 't.description']);
$this->setSortCols(['t.id', 't.created_at'], ['t.id' => 'desc']);
$this->setAllowedLimits([10, 25, 50]);
}
}
findByOrderBy(QueryFilterArgs) to work with the bundle.
public function findByOrderBy(QueryFilterArgs $args): QueryResult
{
// Build query with $args->getSearchBy(), $args->getSortBy(), etc.
}
ConditionManager into repositories to handle complex filtering logic.public function handle(Request $request, Closure $next)
{
$request->merge([
'filter' => $request->query->get('filter', []),
'sortby' => $request->query->get('sortby'),
'sortdir' => $request->query->get('sortdir', 'asc'),
]);
return $next($request);
}
Artprima\QueryFilterBundle\Request\Request to normalize incoming filter parameters.null checks:
$config->setDefaultLimit(10);
$config->setSortCols(['t.id'], ['t.id' => 'asc']);
?limit=50) while enforcing allowed values:
$config->setAllowedLimits([10, 25, 50, 100]);
ConditionManager to support custom DQL operators (e.g., SIMILAR TO).
// In a custom ConditionManager service
public function addCustomCondition(string $field, string $operator, $value)
{
if ($operator === 'custom_op') {
$qb->andWhere("$field SIMILAR TO :val")->setParameter('val', $value);
}
}
connector in advanced mode to chain filters with AND/OR:
GET /items?simple=0&filter[0][field]=t.name&filter[0][type]=like&filter[0][x]=Doe&filter[1][connector]=or&filter[1][field]=t.status&filter[1][type]=eq&filter[1][x]=active
QueryResult into Laravel API Resources for consistent JSON responses.
return ItemResource::collection($data)->additional(['meta' => $meta]);
FormRequest:
public function rules()
{
return [
'filter.*.field' => 'required|in:t.name,t.description',
'filter.*.type' => 'required|in:eq,like,gt',
];
}
setSearchAllowedCols() and setSortCols() use the same alias (e.g., t.name, not name).ConditionManager wraps the original QueryBuilder. Always use $proxyQb->getSortedAndFilteredQueryBuilder() to apply filters/sorting.
// WRONG: Skips filtering
$qb = $this->createQueryBuilder('t')->getQuery();
// RIGHT: Uses proxy
$proxyQb = $this->pqbManager->wrapQueryBuilder($qb);
$qb = $proxyQb->getSortedAndFilteredQueryBuilder($searchBy, $sortBy);
fetch="EAGER" or join in QueryBuilder:
$qb->leftJoin('t.user', 'u')->addSelect('u');
?limit=10000). Enforce a max limit in config:
$config->setAllowedLimits([10, 25, 50, 100]);
setStrictColumns(true) to block invalid fields/sorts, but test thoroughly—it throws UnexpectedValueException on errors.limit or page, the bundle uses defaults. Override in config:
$config->set
How can I help you explore Laravel packages today?