Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Query Filter Bundle Laravel Package

braune-digital/query-filter-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the bundle via Composer (Symfony 4+):

    composer require braune-digital/query-filter-bundle
    

    Register in config/bundles.php:

    return [
        // ...
        BrauneDigital\QueryFilterBundle\BrauneDigitalQueryFilterBundle::class => ['all' => true],
    ];
    
  2. Basic Configuration Define a filter configuration in config/packages/braune_digital_query_filter.yaml:

    braune_digital_query_filter:
        filters:
            product:
                - { name: "category", type: "string", operator: "=", field: "category_id" }
                - { name: "price_min", type: "numeric", operator: ">", field: "price" }
                - { name: "price_max", type: "numeric", operator: "<", field: "price" }
    
  3. First Use Case Inject the QueryFilter service in a controller and apply filters:

    use BrauneDigital\QueryFilterBundle\QueryFilter;
    
    public function listProducts(Request $request, QueryFilter $queryFilter)
    {
        $query = $this->getDoctrine()->getRepository(Product::class)->createQueryBuilder('p');
        $query = $queryFilter->apply($query, $request->query->all(), 'product');
    
        return $this->render('product/list.html.twig', [
            'products' => $query->getQuery()->getResult(),
        ]);
    }
    

Implementation Patterns

Frontend Integration

  • URL-Based Filtering Use query parameters (e.g., ?category=electronics&price_min=50) to pass filters from frontend forms or client-side frameworks (React, Vue).

  • Form Generation Dynamically generate filter forms in Twig:

    {% for filter in filters %}
        {% if filter.type == 'string' %}
            <input type="text" name="{{ filter.name }}" placeholder="{{ filter.name|replace('_', ' ') }}">
        {% elseif filter.type == 'numeric' %}
            <input type="number" name="{{ filter.name }}" placeholder="{{ filter.name|replace('_', ' ') }}">
        {% endif %}
    {% endfor %}
    

Backend Workflows

  • Repository Integration Extend repositories to include filtered queries:

    public function findFiltered(QueryBuilder $qb, array $filters, string $filterGroup)
    {
        return $this->queryFilter->apply($qb, $filters, $filterGroup);
    }
    
  • API Responses Return filtered results in API endpoints:

    public function filterAction(Request $request)
    {
        $query = $this->getDoctrine()->getRepository(Product::class)->createQueryBuilder('p');
        $filteredQuery = $this->queryFilter->apply($query, $request->query->all(), 'product');
    
        return $this->json($filteredQuery->getQuery()->getResult());
    }
    
  • Pagination + Filtering Combine with KnpPaginatorBundle:

    use Knp\Component\Pager\PaginatorInterface;
    
    public function listFiltered(Request $request, PaginatorInterface $paginator, QueryFilter $queryFilter)
    {
        $query = $this->getDoctrine()->getRepository(Product::class)->createQueryBuilder('p');
        $filteredQuery = $queryFilter->apply($query, $request->query->all(), 'product');
        $pagination = $paginator->paginate($filteredQuery, $request->query->getInt('page', 1), 10);
    
        return $this->render('product/list.html.twig', ['pagination' => $pagination]);
    }
    

Advanced Patterns

  • Dynamic Filter Groups Load filter configurations dynamically (e.g., from database) and pass them to the QueryFilter service.

  • Caching Filtered Queries Cache filtered results with tags (e.g., product:category:electronics):

    $filteredQuery = $queryFilter->apply($query, $filters, 'product');
    $cacheKey = md5(serialize($filters));
    return $this->cache->get($cacheKey, function() use ($filteredQuery) {
        return $filteredQuery->getQuery()->getResult();
    }, ['product_filters']);
    

Gotchas and Tips

Common Pitfalls

  • Symfony Version Mismatch Ensure you’re using the correct branch (1.4.x for Symfony 4/5). Mixing versions may break dependency resolution.

  • Case Sensitivity in Filters By default, string filters are case-sensitive. Use operator: "LIKE" for case-insensitive searches:

    - { name: "name", type: "string", operator: "LIKE", field: "name" }
    
  • Numeric Range Quirks Ensure price_min and price_max are not passed together without values (e.g., ?price_min=). Add validation:

    if ($request->query->has('price_min') && empty($request->query->get('price_min'))) {
        $request->query->remove('price_min');
    }
    
  • Doctrine Field Mismatches If field in YAML doesn’t match the entity property (e.g., field: "category_id" vs. field: "category"), the filter will fail silently. Use dump($query->getSQL()) to debug.

Debugging Tips

  • Inspect Generated SQL Log the final query to verify filters are applied correctly:

    $filteredQuery = $queryFilter->apply($query, $filters, 'product');
    error_log($filteredQuery->getSQL());
    
  • Enable Query Logging In config/packages/dev/doctrine.yaml:

    doctrine:
        dbal:
            logging: true
            profiling: true
    
  • Validate Filter Groups Always check if a filter group exists before applying:

    if (!$queryFilter->hasGroup('product')) {
        throw new \InvalidArgumentException('Filter group "product" not configured.');
    }
    

Extension Points

  • Custom Operators Extend the bundle by adding custom operators (e.g., IN, NOT IN):

    use BrauneDigital\QueryFilterBundle\Operator\OperatorInterface;
    
    class InOperator implements OperatorInterface
    {
        public function apply(QueryBuilder $qb, string $field, $value, string $alias)
        {
            return $qb->andWhere($alias . '.' . $field . ' IN (:value)')
                ->setParameter('value', $value);
        }
    }
    

    Register in services.yaml:

    services:
        BrauneDigital\QueryFilterBundle\Operator\InOperator:
            tags: { name: braune_digital_query_filter.operator, alias: 'in' }
    
  • Dynamic Filter Loading Override the FilterLoader to load configurations from a database or API:

    use BrauneDigital\QueryFilterBundle\Loader\FilterLoaderInterface;
    
    class DatabaseFilterLoader implements FilterLoaderInterface
    {
        public function load(string $group): array
        {
            // Fetch from DB/API and return array of filters
            return $this->entityManager->getRepository(FilterConfig::class)
                ->findBy(['group' => $group]);
        }
    }
    
  • Localization Support Translate filter names/placeholders in Twig:

    <label>{{ 'filter.product.category'|trans }}</label>
    <input type="text" name="category" placeholder="{{ 'filter.product.category_placeholder'|trans }}">
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours