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

Form Filter Bundle Laravel Package

lexik/form-filter-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require spiriitlabs/form-filter-bundle
    

    Add to config/bundles.php (Symfony) or config/packages/lexik_form_filter.yaml (Laravel via Symfony bridge):

    SpiriitLabs\FormFilterBundle\SpiriitLabsFormFilterBundle::class => ['all' => true],
    
  2. First Use Case: Filtering a Doctrine Query Define a filter DTO (Data Transfer Object) for your entity:

    // src/Filter/ProductFilter.php
    namespace App\Filter;
    
    use SpiriitLabs\FormFilterBundle\Filter\FormFilterBuilder;
    
    class ProductFilter
    {
        public ?string $name;
        public ?int $minPrice;
        public ?int $maxPrice;
    }
    

    Build the filter in a controller/service:

    use SpiriitLabs\FormFilterBundle\Filter\FormFilterBuilder;
    use Doctrine\ORM\EntityManagerInterface;
    
    public function index(Request $request, EntityManagerInterface $em)
    {
        $filter = new ProductFilter();
        FormFilterBuilder::build($request, $filter);
    
        $queryBuilder = $em->getRepository(Product::class)->createQueryBuilder('p');
        $queryBuilder = (new FormFilterBuilder())
            ->add('name', \Doctrine\ORM\Query\Expr\Comparison::LIKE, 'p.name')
            ->addRange('price', 'p.price', $filter->minPrice, $filter->maxPrice)
            ->build($queryBuilder);
    
        $products = $queryBuilder->getQuery()->getResult();
        return $this->render('product/index.html.twig', ['products' => $products]);
    }
    
  3. Key Files to Review

    • src/Filter/FormFilterBuilder.php (Core logic)
    • src/DependencyInjection/ (Configuration)
    • tests/ (Usage examples)

Implementation Patterns

Common Workflows

1. Form Filter DTO Design

  • Naming: Use *Filter suffix (e.g., UserFilter, OrderFilter).
  • Properties: Mirror queryable fields; use nullable types (?string, ?int) for optional filters.
  • Complex Types: For nested objects, use FilterCollection or custom builders.
    class UserFilter {
        public ?string $name;
        public ?FilterCollection $roles; // For multi-select filters
    }
    

2. Query Builder Integration

  • Chaining: Build filters incrementally for modularity.
    $qb = $em->getRepository(Product::class)->createQueryBuilder('p');
    $qb = (new FormFilterBuilder())
        ->add('name', 'LIKE', 'p.name')
        ->addRange('price', 'p.price', $filter->minPrice, $filter->maxPrice)
        ->addDateRange('createdAt', 'p.createdAt', $filter->startDate, $filter->endDate)
        ->build($qb);
    
  • Dynamic Joins: Use addJoin() for related entities.
    ->addJoin('p.category', 'c')
    ->add('category.name', 'LIKE', 'c.name')
    

3. Symfony Form Integration

  • Bind filter DTO to a Symfony form for validation and UI:
    $form = $this->createForm(ProductFilterType::class, $filter);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        // Build query with validated data
    }
    
  • Type Example:
    class ProductFilterType extends AbstractType {
        public function buildForm(FormBuilderInterface $builder, array $options) {
            $builder
                ->add('name', TextType::class)
                ->add('minPrice', IntegerType::class, ['required' => false])
                ->add('maxPrice', IntegerType::class, ['required' => false]);
        }
    }
    

4. Pagination & Sorting

  • Combine with KnpPaginator or native Doctrine pagination:
    use Knp\Component\Pager\PaginatorInterface;
    
    $paginator = $this->get('knp_paginator');
    $results = $paginator->paginate(
        $queryBuilder,
        $request->query->getInt('page', 1),
        10
    );
    

5. API Use Cases

  • For APIs, bind filters directly from JSON:
    $filter = new ProductFilter();
    $json = json_decode($request->getContent(), true);
    FormFilterBuilder::buildFromArray($json, $filter);
    

Integration Tips

Laravel-Specific Adaptations

  1. Service Container Binding Bind the builder to Laravel’s container for dependency injection:

    // config/app.php
    'bindings' => [
        SpiriitLabs\FormFilterBundle\Filter\FormFilterBuilder::class => function ($app) {
            return new \SpiriitLabs\FormFilterBundle\Filter\FormFilterBuilder();
        },
    ];
    
  2. Request Handling Use Laravel’s Request facade or inject Request directly:

    use Illuminate\Http\Request;
    
    public function index(Request $request) {
        $filter = new ProductFilter();
        \SpiriitLabs\FormFilterBundle\Filter\FormFilterBuilder::build($request, $filter);
        // ...
    }
    
  3. Eloquent Integration For Eloquent queries, wrap the builder in a helper:

    // app/Helpers/FilterHelper.php
    class FilterHelper {
        public static function apply(Builder $query, array $filterData) {
            $filter = new ProductFilter();
            FormFilterBuilder::buildFromArray($filterData, $filter);
            $builder = new FormFilterBuilder();
            // Map Doctrine expressions to Eloquent
            $query->where($builder->getConditions($filter));
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Case Sensitivity in LIKE Queries

    • Doctrine’s LIKE is case-sensitive by default. Use LOWER() for case-insensitive searches:
      ->add('name', 'LIKE', 'LOWER(p.name)')
      
    • Or configure collation in your database.
  2. Null Handling

    • Uninitialized filter properties (null) are ignored by default. To include them:
      ->add('name', 'LIKE', 'p.name', true) // Force include even if null
      
    • For ranges, ensure both bounds are provided or handle partial ranges:
      if ($filter->minPrice !== null || $filter->maxPrice !== null) {
          $qb->andWhere('p.price BETWEEN :min AND :max')
              ->setParameter('min', $filter->minPrice ?? 0)
              ->setParameter('max', $filter->maxPrice ?? PHP_INT_MAX);
      }
      
  3. Circular References

    • Avoid circular references in filter DTOs (e.g., UserFilter referencing OrderFilter which references UserFilter). Use FilterCollection for complex hierarchies.
  4. Performance with ORM

    • Complex filters can lead to N+1 queries. Use fetch="EAGER" or DQL JOIN explicitly:
      ->addJoin('p.category', 'c', 'WITH', 'c.id = p.categoryId')
      
  5. Archived Status


Debugging Tips

  1. Query Logging Enable Doctrine logging to inspect generated queries:

    $em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
    

    Or use Laravel’s query logging:

    \DB::enableQueryLog();
    $queryBuilder->getQuery()->getSQL(); // Inspect the final query
    
  2. Filter Validation

    • Validate filter DTOs manually if needed:
      if ($filter->minPrice > $filter->maxPrice) {
          throw new \InvalidArgumentException("Min price cannot exceed max price.");
      }
      
  3. Parameter Binding

    • Debug parameter binding issues by dumping the query:
      $query = $queryBuilder->getQuery();
      dump($query->getParameters());
      

Extension Points

  1. Custom Filter Types
    • Extend FormFilterBuilder to add domain-specific filters:
      class CustomFormFilterBuilder extends FormFilterBuilder {
          public function addCustomCondition(string $field, string $operator, string $path, $value) {
              // Custom logic (e.g., full-text search)
              $this->conditions[] = $this->expr->func('MATCH_AGAINST', $path, ':value');
              $this
      
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.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware