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

Filter Sorter Bundle Laravel Package

bartlomiejbeta/filter-sorter-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require bartlomiejbeta/filter-sorter-bundle
    

    Register the bundle in config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):

    BartB\FilterSorterBundle\FilterSorterBundle::class => ['all' => true],
    
  2. Extend Repository Replace your base repository with AbstractEntitySpecificationAwareRepository:

    use BartB\FilterSorterBundle\Repository\AbstractEntitySpecificationAwareRepository;
    
    class CarRepository extends AbstractEntitySpecificationAwareRepository
    {
        public function __construct(EntityManagerInterface $em)
        {
            parent::__construct($em, $this->getEntityName());
        }
    
        protected function getEntityName(): string
        {
            return Car::class;
        }
    }
    
  3. First Filter/Sort Endpoint Create a controller to handle filtered/sorted queries:

    use BartB\FilterSorterBundle\Manager\FilterQueryManager;
    use BartB\FilterSorterBundle\Converter\FilterParamConverter;
    use BartB\FilterSorterBundle\Converter\SortParamConverter;
    
    class CarController extends AbstractController
    {
        public function list(FilterQueryManager $filterQueryManager, FilterParamConverter $filterConverter, SortParamConverter $sortConverter)
        {
            $filter = $filterConverter->convert($this->get('request')->query->all());
            $sort = $sortConverter->convert($this->get('request')->query->all());
    
            $queryBuilder = $filterQueryManager->getQueryBuilder(
                Car::class,
                $filter,
                $sort
            );
    
            return $this->json($queryBuilder->getQuery()->getResult());
        }
    }
    
  4. Define a Specification Create a simple specification for filtering (e.g., CarMakeSpecification):

    use BartB\FilterSorterBundle\Specification\SpecificationInterface;
    
    class CarMakeSpecification implements SpecificationInterface
    {
        private $make;
    
        public function __construct(string $make)
        {
            $this->make = $make;
        }
    
        public function isSatisfiedBy(object $car): bool
        {
            return $car->getMake() === $this->make;
        }
    }
    
  5. Register Filter/Sort Services Configure filters/sorts in services.yaml:

    services:
        App\Filter\CarMakeFilter:
            tags:
                - { name: filter_sorter.filter, entity: App\Entity\Car, alias: make }
        App\Sort\CarMakeSort:
            tags:
                - { name: filter_sorter.sort, entity: App\Entity\Car, alias: make }
    
  6. First API Call Hit your endpoint with query params:

    /api/cars?filter[make]=Toyota&sort[make]=asc
    

Implementation Patterns

Workflow: Building Filtered/Sorted APIs

  1. Filter Creation

    • Define filter services for each entity property (e.g., CarMakeFilter, CarYearFilter).
    • Use FilterParamConverter to parse query params (e.g., ?filter[make]=Toyota).
    • Example filter service:
      class CarMakeFilter implements FilterInterface
      {
          public function getSpecification(array $params): SpecificationInterface
          {
              return new CarMakeSpecification($params['make']);
          }
      }
      
  2. Sorting Integration

    • Define sort services (e.g., CarMakeSort, CarPriceSort).
    • Use SortParamConverter to parse sort params (e.g., ?sort[make]=asc).
    • Example sort service:
      class CarMakeSort implements SortInterface
      {
          public function getOrderBy(array $params): string
          {
              return $params['direction'] === 'desc' ? 'c.make DESC' : 'c.make ASC';
          }
      }
      
  3. Query Construction

    • Use FilterQueryManager to combine filters and sorts:
      $queryBuilder = $filterQueryManager->getQueryBuilder(
          Car::class,
          $filter,
          $sort
      );
      
    • Chain specifications with SpecificationCollection:
      $collection = new SpecificationCollection();
      $collection->add($makeSpec, $yearSpec);
      
  4. Pagination

    • Manually add pagination to the query builder:
      $queryBuilder->setFirstResult($page * $limit)
                   ->setMaxResults($limit);
      
  5. Reusable Filter Groups

    • Group filters by alias (e.g., advanced, basic) in services.yaml:
      tags:
          - { name: filter_sorter.filter, entity: App\Entity\Car, alias: advanced, group: advanced_filters }
      
    • Enable groups via query param (e.g., ?filter_group=advanced).

Integration Tips

  1. Symfony Forms

    • Use FilterParamConverter to bind query params to form data:
      $filterData = $filterConverter->convert($request->query->all());
      $form = $this->createForm(CarFilterType::class, $filterData);
      
  2. Doctrine Extensions

    • Combine with DoctrineExtensions for advanced filtering (e.g., Like, NotLike):
      $queryBuilder->andWhere('c.name LIKE :name')
                   ->setParameter('name', '%' . $filter['name'] . '%');
      
  3. API Platform

    • Override getCollectionOperations to inject filtering/sorting:
      public function getCollectionOperations(EntityManagerInterface $manager)
      {
          $collection = parent::getCollectionOperations($manager);
          $collection['get']['filters'] = 'filter[make][operator][eq]';
          return $collection;
      }
      
  4. Caching Queries

    • Cache SpecificationCollection or query builder results:
      $cacheKey = md5(serialize($filter) . serialize($sort));
      if (!$cachedResult = $cache->get($cacheKey)) {
          $result = $queryBuilder->getQuery()->getResult();
          $cache->set($cacheKey, $result, 3600);
      }
      
  5. Validation

    • Validate filter/sort params early:
      $validator = $this->get('validator');
      $errors = $validator->validate($filter);
      if (count($errors) > 0) {
          throw new \InvalidArgumentException((string) $errors);
      }
      

Gotchas and Tips

Pitfalls

  1. Deprecated Symfony 3 Syntax

    • The bundle assumes Symfony 3.x conventions (e.g., AppKernel). For Symfony 4/5:
      • Use config/bundles.php instead of AppKernel.
      • Replace get() with dependency injection (e.g., FilterQueryManager $filterQueryManager).
  2. Query Builder Overrides

    • If you extend AbstractEntitySpecificationAwareRepository, ensure getEntityName() is implemented correctly. Misconfiguration leads to:
      [Semantical Error] The entity name could not be found.
      
  3. Specification Performance

    • Avoid complex logic in isSatisfiedBy(). Use SpecificationCollection for chaining:
      // Bad: Nested conditions in one spec.
      public function isSatisfiedBy($car) {
          return $car->getMake() === $make && $car->getYear() > 2010;
      }
      // Good: Split into separate specs.
      
  4. Sorting Ambiguity

    • Sort params like ?sort[make]=asc may conflict with entity property names. Use aliases:
      tags:
          - { name: filter_sorter.sort, entity: App\Entity\Car, alias: manufacturer }
      
      Then call ?sort[manufacturer]=asc.
  5. Case Sensitivity

    • Filter comparisons (e.g., make === 'Toyota') are case-sensitive by default. Normalize input:
      $this->make = strtolower($make);
      
  6. Circular Dependencies

    • Avoid injecting FilterQueryManager into filter/sort services to prevent circular references.

Debugging

  1. Query Dump

    • Inspect the final query builder:
      dump($queryBuilder->getQuery()->getSQL());
      dump($queryBuilder->getQuery()->getParameters());
      
  2. Specification Logging

    • Add debug output in isSatisfiedBy() to trace filtering:
      error_log("Checking make: {$this->make} against car: " . $car->getMake());
      
  3. ParamConverter Issues

    • If FilterParamConverter fails silently, enable debug mode and check:
      php bin/console debug:container filter_param_converter
      
  4. Entity Not Found

    • Verify `getEntity
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.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle