Weave 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 Manager Bundle Laravel Package

ongr/filter-manager-bundle

Symfony bundle providing a filter manager for building and applying Elasticsearch filters to searches. Integrates with ONGR ElasticsearchBundle so you can define, register, and reuse filters like terms and ranges cleanly across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and register. This package isn't a standalone ES client—it's a Symfony bundle that sits on top of ongr/elasticsearch-bundle. Install both, then register Ongr\FilterManagerBundle\OngrFilterManagerBundle in AppKernel/bundles.php. It pulls in an ES connection config, so have ongr_elasticsearch configured and a working index before you start.

  2. Look first at the bundle's own README.md and the Resources/config/services.yml — the service IDs (ongr_filter_manager.filter_manager, the repository wrapper, and the tag ongr_filter_manager.filter) are the real entry points. The README's YAML examples are the most useful documentation; the Filter/ and Repository/ directories are small and readable.

  3. First use case: a product listing page with facet filters. Define one manager (a named group of filters + a repository) and the filters underneath it in config.yml:

    ongr_filter_manager:
        managers:
            product_list:
                repository: es.manager.default.product
                filters: [color, price, brand]
        filters:
            color:
                type: multi_choice
                request_field: color         # ?color=red,blue
                field: color
                document_class: AppBundle:Product
                aggregations:
                    name: color
                    type: terms
                    size: 20
                    load_choices: true
            price:
                type: range
                request_field: price
                field: price
                document_class: AppBundle:Product
                aggregations:
                    name: price
                    type: range
                    ranges:
                        - { from: 0,  to: 50 }
                        - { from: 50, to: 100 }
                    load_choices: true
    
  4. Wire it in a controller. Resolve the named manager, hand its repository to your view, and read the documents/aggregations it produced. Typical shape:

    $filterManager = $this->get('ongr_filter_manager.filter_manager_repository')
                          ->getManager('product_list');
    
    return $this->render('product/list.html.twig', [
        'filterManager' => $filterManager,
        'products'      => $filterManager->getRepository()->getDocuments(),
    ]);
    

    Confirm the exact manager-resolver service ID against the bundle's services.yml — that's the one place where this bundle varies across versions.

  5. Render in Twig by looping filters → choices, not by hand-building query strings:

    {% for filter in filterManager.getFilters() %}
        <h3>{{ filter.name }}</h3>
        <ul>
        {% for choice in filter.choices %}
            <li>
                <a href="{{ choice.url }}">{{ choice.name }} ({{ choice.count }})</a>
            </li>
        {% endfor %}
        </ul>
    {% endfor %}
    

    The first end-to-end win is a filter sidebar whose links toggle ES terms aggregations while the product grid re-query reflects the active filters.

Implementation Patterns

  • One manager per page section. Define product_list, category_list, brand_list as separate managers. Each manager owns its own repository and filter set, so a category sidebar and a product grid never interfere. This is the intended usage—managers are cheap to define.

  • Separate "search" filters from "facet" filters. A search filter (free text) maps to a query clause, while choice/multi_choice/range filters map to aggregations. Keep them in the same manager but expect them to apply differently to the ES query vs. post-filter. If you need facet counts to reflect the whole corpus rather than the filtered one, look for the apply_to_aggregations option on individual filters (or its equivalent in your version) before you start hand-writing post-filters.

  • Multi-choice is the default for product lists. multi_choice reads an array from a single request param (?color=red&color=blue or comma-separated depending on config). choice is single-value and is a better fit for "sort" or "category" selectors. Range filters (range, date_range, boolean) derive choices from your YAML range definitions rather than the ES aggregation—so range filter counts behave differently from term counts; don't be surprised.

  • load_choices: true is effectively required. Without it the aggregation is built but getChoices() returns an empty array. This is the single most common "why is my sidebar blank" cause.

  • Custom filters: extend AbstractFilter, implement getChoices() and isActive(), tag the service. That's the extension point. Register the service with ongr_filter_manager.filter (optionally with a type attribute matching your YAML type). This is the clean way to add things like "in stock" booleans driven by non-ES logic or "on sale" filters that need a custom query clause.

  • Controller→Twig contract. Expose the manager as-is and let Twig call getFilters(), getActiveFilters(), and filter.isActive(). That keeps URL-generation logic (adding/removing a param, preserving the others) centralized in Choice::getUrl() instead of sprinkled across templates. For pagination, pass along the same manager so page-2 links retain active filters.

  • Compose with a paginator. The manager's repository gives you the filtered query; hand that query to Knp/Pagerfanta in the usual ES way. Don't rebuild the query yourself—you'll bypass the filter application and get counts that don't match the sidebar.

  • Multiple managers per request are fine and expected; resolve each by name in the controller. Share the ES connection/config, not the manager.

Gotchas and Tips

  • This is a 2022-era package with a small footprint (26 stars, 0 direct dependents). Pin Symfony and ongr/elasticsearch-bundle versions together and expect to do compatibility work (e.g. Filter type hints, container aliasing) if you're on Symfony 5+/6+. There is no active maintenance; treat the source as the documentation of record.

  • ES mapping is your problem, not the bundle's. A terms aggregation on a text-analyzed field returns buckets per token, not per product—so color: "dark red" becomes two buckets. Use keyword fields or sub-fields (field: color.keyword) in the filter config. This bites everyone the first time.

  • Aggregation size defaults to 10. If you have 30 colors, silently you get 10 choices. Set size explicitly on every terms aggregation, or you'll chase a "missing facet values" bug for a while.

  • Active detection depends on the request. Filters read from the current Request via the request stack. In long-running workers (Roadrunner, Swoole, Messenger consumers) the manager is not request-safe—instantiate/scoped it per request or reset it, or you'll leak filters between requests.

  • Debug by dumping the generated query. Before you fight the sidebar, dump the repository's query ($manager->getRepository()->getQuery() or the equivalent getter) to see the exact aggregation and post_filter payload. Compare it to what you'd send to _search by hand. 90% of "filter isn't applied" cases are visible in that payload.

  • document_class must match the repository's index type. Mismatches produce empty aggregations rather than errors, which makes them look like config typos. Cross-check the repository alias, the document class, and the ES index.

  • URL params must survive your router. Choice URLs are generated from the current request plus the toggle. If you use localized routes or custom slugs that strip unknown query strings, filter URLs silently break. Test a choice link with two active filters before going further.

  • Range filters don't get counts from ES. They're rendered from the ranges config, so counts are either absent or computed separately. If you need count-per-range, add a redundant range aggregation and read it yourself, or build a custom filter.

  • Extension points, in order of usefulness: (1) custom filter class extending AbstractFilter with the ongr_filter_manager.filter tag; (2) overriding the Twig template that renders filters/choices; (3) configuring apply_to_aggregations/post-filter behavior per filter. Avoid subclassing the manager or repository—those internals are the least stable surface.

  • Caching: filter configs and manager definitions can be cached by the container; aggregations cannot. If you cache the rendered sidebar, key it on the full set of active request params, not just the page.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect