Skip to content

Refactor Filter Processing Architecture and Introduce CQRS-Based Domain Model#5

Open
ericges wants to merge 38 commits intomainfrom
feat/refactor-abstraction
Open

Refactor Filter Processing Architecture and Introduce CQRS-Based Domain Model#5
ericges wants to merge 38 commits intomainfrom
feat/refactor-abstraction

Conversation

@ericges
Copy link
Contributor

@ericges ericges commented Jan 28, 2026

Summary

This PR replaces the monolithic manager-based architecture with a layered, contract-driven domain model built around three core concerns: Specification (what a list is), Engine (how results are produced), and Query (how the database query is built).

Specification Layer

  • ListSpecification describes a list via its type, data-container table, data source reference, and a FilterDefinitionCollection
  • FilterDefinition represents a single filter rule with type, alias, target alias, intrinsic flag, and dynamic properties
  • FilterCollectorInterface (with ListModelFilterCollector) handles filter collection from various data sources
  • ListSpecificationFactory orchestrates construction and dispatches ListSpecificationCreatedEvent for post-hoc filter injection

Engine (Projector / Context / View)

  • Engine is a thin orchestrator that pairs a ContextInterface with a ListSpecification and delegates to a ProjectorRegistry
  • Contexts describe the purpose of evaluation:
    • InteractiveContext — full frontend list with form, pagination, sorting, and reader links
    • AggregationContext — COUNT-only evaluation with filter values
    • ValidationContext — single-record lookup for the reader with lazy entry caching
  • Projectors (ProjectorInterface) implement the actual data fetching strategy. InteractiveProjector handles form building, runs an aggregation sub-projection for total count, builds the paginator, and returns a lazy view. AggregationProjector and ValidationProjector handle their respective contexts. Priority-based resolution allows integration modules to override projectors per list type
  • Views are lightweight result objects holding lazy closures (InteractiveView, AggregationView, ValidationView), with traits for model hydration (HandlesModelsTrait) and reader URL generation (LinksToReaderTrait)

Query Subsystem

  • Struct-based query building: SqlQueryStruct accumulates all parts of a SELECT statement, then QueryBuilderFactory materialises it into a Doctrine DBAL QueryBuilder
  • TableAliasRegistry manages table aliases with activation, hiding, dependency tracking, and topological join resolution
  • FilterExecutor iterates filter definitions and delegates to FilterInvokerResolver, which supports context-specific overrides via #[AsFilterInvoker] attribute and FilterInvokerRegistry
  • ListQueryDirector orchestrates the full pipeline: execution context creation → filter invocation → ModifyListQueryStructEvent dispatch → query builder materialisation
  • Modular query struct listeners handle SELECT, CONDITIONS, JOINS, ORDER, PAGINATION, and GROUP BY at defined priorities
  • New contracts IntrinsicValueContract and RuntimeValueContract standardise filter value resolution

Paginator

  • PaginatorConfig as an immutable value object with computed page metadata
  • Paginator extends config with URL generation, window-based navigation, and ellipsis support
  • PaginatorFactory replaces PaginatorBuilder with request-aware construction

Integration Modules

  • Contao Calendar: EventsInteractiveProjector and EventsAggregationProjector override default projectors for event-specific logic (multi-day/recurring event grouping via GroupsEntriesTrait)
  • Contao Comments: relocated to src/Integration/ContaoComments/
  • Terminal42 Changelanguage: relocated to src/Integration/Terminal42Changelanguage/

Removed

The following components have been removed in favour of the new architecture:

  • ListViewManager, ListView, ListViewBuilder, ListViewResolver — replaced by Engine/Projector/View
  • ReaderManager — replaced by ValidationProjector + ValidationView
  • ListQueryManager, ListQueryBuilder — replaced by ListQueryDirector + SqlQueryStruct + QueryBuilderFactory
  • FilterContextManager, FilterContext, FilterContextBuilder, FilterContextCollection — replaced by FilterExecutor + FilterInvocation
  • ListItemProvider, ListItemProviderManager, ListItemProviderInterface — inlined into projectors
  • TemplateManager, TranslationManager — no longer needed
  • Various DTOs (ContentContext, SqlQuery, ParameterizedSqlQuery, FilterInvocationDto, etc.)
  • ArrayFieldChoice, SortDescriptor, PresetFiltersConfig

Added

  • src/Engine/ — full Context/Projector/View layer
  • src/Query/ — struct-based query subsystem with TableAliasRegistry, FilterExecutor, ListQueryDirector
  • src/Specification/ListSpecification, FilterDefinition, and factories
  • src/FilterCollector/ — pluggable filter collection interface
  • src/Integration/ — relocated and restructured integration modules
  • src/Sort/SortOrder and SortOrderSequence replacing SortDescriptor
  • src/DataCollector/FlareCollector.php — Symfony profiler integration
  • #[AsFilterInvoker] attribute and compiler pass for context-specific filter invocation
  • ConfigureQueryContract for list types to register joins and base query modifications
  • Translation files for filter and list labels

Breaking Changes

This is a full architectural rewrite. All public APIs from the removed components are gone. Consumers must migrate to the Engine/Projector/View pattern. Key migration points:

  • ListViewManager::render()EngineFactory + Engine::createView() with InteractiveContext
  • ReaderManagerEngine::createView() with ValidationContext
  • ListQueryManager / ListQueryBuilderListQueryDirector (internal, typically not called directly)
  • FilterContextManager → filter values are now resolved automatically by AbstractProjector::gatherFilterValues()
  • Custom ListItemProviderInterface implementations → implement ConfigureQueryContract on the list type and/or use #[AsFilterInvoker] for context-specific filter behaviour
  • Custom filter elements → implement IntrinsicValueContract / RuntimeValueContract as needed; the __invoke method signature now receives (FilterInvocation, FilterQueryBuilder)

Test Plan

  • Verify interactive list rendering with pagination, sorting, and filter form submission
  • Verify reader page rendering with auto-item resolution
  • Verify calendar events list with recurring/multi-day event grouping and correct aggregation counts
  • Verify filter elements (boolean, date range, search keywords, field value choice, published, archive relation)
  • Verify paginator navigation with ellipsis rendering across edge cases (first/last/middle pages)
  • Verify breadcrumb listener with reader page titles
  • Verify Changelanguage integration with multilingual lists
  • Verify Contao Comments integration on reader pages
  • Verify Symfony profiler data collector output

@ericges ericges self-assigned this Jan 28, 2026
@ericges ericges changed the title Refactor architecture Refactor Filter Processing Architecture and Introduce CQRS-Based Domain Model Jan 30, 2026
@ericges ericges force-pushed the feat/refactor-abstraction branch from ca566db to 61239dc Compare February 20, 2026 09:05
@ericges ericges requested a review from Copilot February 20, 2026 09:06

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

@ericges ericges force-pushed the feat/refactor-abstraction branch from 2812721 to 30d539e Compare March 3, 2026 14:49
ericges added 14 commits March 3, 2026 16:38
…ed architecture for improved flexibility and modularity
… for consistency with updated event naming conventions
ericges and others added 23 commits March 4, 2026 14:13
…eamline codebase and align with view engine architecture
…ions, stricter type hints, and deprecated method removals
…hance dynamic property handling in FilterModel
…FilterDefinition` and related classes for improved flexibility
…for method alignment and consistency across filter elements
…gsManagerFinderPass` with `CodefogTagsPass`, adding registry and resolver classes, and enhancing dependency injection alignment
…rays with typed objects for improved type safety and readability in CodefogTags integration
…place table alias attributes with a registry-based implementation in CodefogTags integration
…to ensure array type consistency and handle numeric values as arrays
…gTagsChoiceElement`, update behavior in `PaginatorFactory` and event listeners
- Updated `AGENTS.md` to document new Makefile commands for managing PHP and Composer tasks via Docker.
- Added new `Dockerfile` for the PHP environment with required extensions and dependencies.
- Introduced `docker-compose.yml` for containerized development.
- Modified `Makefile` to include `php` and `composer` commands.
- Added `.dockerignore` to exclude unnecessary files from Docker context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants