List Specs & Filters
1. Overview
The ListSpec is the "Source of Truth" in Flare: a static, context-independent description of what data
should be fetched and how it should be filtered. It contains no DCA or storage specifics — translating a
stored tl_flare_list row into config is the job of the build lifecycle.
2. The ListSpec
Class: HeimrichHannot\FlareBundle\List\ListSpec (final readonly)
Key Properties:
driver: The list driver instance (ListDriverInterface) — a registered service (e.g. theflare_newsdriver) or an inline instance.dc: The main data container table of the list. It is definitively resolved at creation time — via the driver'sgetDataContainerName(array $config)— and baked into the canonical config (config['dc']); a spec with an unresolvable table cannot be created.filters: A keyed map (array<string, Filter>) of the list's filters.config: The canonical config, resolved through the base schema (BaseListOptions) and the driver's own schema.source: Optional provenance for error messages, e.g.tl_flare_list.5.
ListSpec is immutable. Deriving methods return new instances:
public function withFilter(Filter $filter, ?string $key = null): self;
public function withoutFilter(string $key): self;
public function withFilters(array $filters): self;
public function withConfig(array $config): self;
public function hasFilterInstance(string $class): bool;
- Adding filters:
withFilter($filter)keys the filter by its alias, or auto-generates a key for alias-less filters. - Overriding filters:
withFilter($filter, 'published')replaces an existing'published'entry — this is how a manual filter overrides a database-driven filter sharing the same key (e.g. the defaultpublishedfilter that the News list driver adds). - Checking for element classes:
hasFilterInstance(BooleanFilterElement::class)checks whether any filter's element is an instance of the given class or interface.
3. Building a List (ListSpecBuilder)
Specs are built by the ListSpecBuilder (HeimrichHannot\FlareBundle\List\ListSpecBuilder), created
through the ListSpecBuilderFactory — content elements use createFromListModel(ListModel $model).
build() runs the lifecycle:
- The driver's
buildList()hook (if it implementsBuildListContract) adds filters or config overrides. - The
ListBuildEvent(flare.list.{type}.build) gives third parties the same access. - The config is assembled: the framework's
BaseListOptionstranslates the stored model's base columns (includingdc), the driver's transformers translate its own columns, and explicitset()overrides win over both. - The builder delegates to the
ListSpecFactory, which resolves the config against the base and driver schemas, definitively resolves the main data container via the driver'sgetDataContainerName()(throwing if it comes up empty), and freezes the result into the returnedListSpec.
While building, the mutable builder exposes addFilter(), removeFilter(), set(key, value), and read
access to the driver, the stored model, and the filters added so far.
The ListSpecFactory (HeimrichHannot\FlareBundle\List\Factory\ListSpecFactory) is also the programmatic
construction path: create($driver, $filters, $config, $source) accepts a driver instance or a registered
type alias and is the only place ListSpec instances are made.
4. Filters (Filter)
Each entry in the spec's filter map is an immutable
HeimrichHannot\FlareBundle\Filter\Filter value object pairing a filter element with its canonical config —
see the Filter Pipeline for the full property reference.
Key points at the spec level:
element: The filter element instance — a registered service (created viaFilterFactoryfrom a type alias likeflare_bool) or an inline instance.alias: The filter's form name.targetAlias: The SQL table alias the filter should target. Defaults tonull, which resolves tomainwhen the filter is executed. UsewithTargetAlias(string $alias)to pin a filter to a specific alias — it returns a new instance, sinceFilteris immutable.
5. Persistence & Hashing
ListSpec::hash() produces a stable hash from the list's type, data container, source, config, and each
filter's Filter::fingerprint(). The Engine and Projector use it to determine if the configuration has
changed, which is crucial for caching and identifying unique list states (e.g., for pagination parameters).