diff --git a/AGENTS.md b/AGENTS.md index 24d0a1dc..230ae474 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,13 +1,13 @@ # AGENTS.md -This file provides guidance AI coding agents, e.g., Claude Code (claude.ai/code), Gemini, and ChatGPT Codex, when working with code in this repository. +This file provides guidance to AI coding agents, e.g., Claude Code (claude.ai/code), Gemini, and ChatGPT Codex, when working with code in this repository. `CLAUDE.md` is a symlink to this file — always edit `AGENTS.md`. ## Project **FLARE** (Filter, List, And REader) — a Symfony bundle for Contao CMS that replaces complex module setups with a unified List/Reader content element approach. - Namespace: `HeimrichHannot\FlareBundle\` -- Requires PHP ^8.2, Symfony ^5.4/^6.0, Contao ^4.13/^5.0 +- Requires PHP ^8.2, Symfony ^5.4/^6.0/^7.0, Contao ^4.13/^5.0 - Bundle Name: Contao Flare Bundle (`heimrichhannot/contao-flare-bundle`) - Type: Symfony Bundle for Contao CMS - Status: Active development (Beta/WIP). @@ -18,14 +18,27 @@ The core execution flow is: ``` ContentElement Controller - → EngineFactory → Engine - → Context (Interactive / Validation / Aggregation / Export) - → Projector (orchestrates query + filter execution) - → View (formats data for Twig template) + → EngineFactory (consumes a ListSpecification from src/Specification/) + → Engine + → Context (Interactive / Validation / Aggregation) — src/Engine/Context/ + → Loader (src/Engine/Loader/) + Mods (src/Engine/Mod/) + → Projector (Interactive / Validation / Aggregation / Export) — orchestrates query + filter execution + → View (formats data for Twig template) ``` +Note: Contexts and Projectors/Views are separate axes — (Export Context/Projector/View are currently not implemented). + The bundle follows standard Symfony Bundle architecture with deep Contao integration. +**Notable subsystems** (beyond the flow above): +- `src/Specification/` — `ListSpecification` / `FilterDefinition`, the declarative input to the engine +- `src/Filter/`, `src/FilterElement/`, `src/FilterCollector/` — filter definition and execution +- `src/Form/` — filter form building (FilterFormFactory etc.) +- `src/Reader/` — reader/detail-page URL generation (`ReaderUrlGenerator`) +- `src/InferPtable/` — parent-table inference for DCAs +- `src/Integration/` — optional integrations (Codefog Tags, Terminal42 ChangeLanguage), wired via `config/integrations/*.yaml` +- `src/EventListener/NamedDispatch/` — re-dispatches base events under dynamic names (see Event system below) + **Key entry points:** - `src/HeimrichHannotFlareBundle.php` — bundle entry, registers compiler passes - `src/DependencyInjection/HeimrichHannotFlareExtension.php` — DI setup @@ -36,13 +49,16 @@ The bundle follows standard Symfony Bundle architecture with deep Contao integra - `#[AsFilterElement(type: '...', palette: '...', formType: ...)]` — register a filter element - `#[AsListType(type: '...', dataContainer: '...', palette: '...')]` — register a list type - `#[AsFilterCallback(type, 'path.to.callback')]` — register a Contao DCA callback on a filter type +- `#[AsListCallback(type, 'path.to.callback')]` — register a Contao DCA callback on a list type - `#[AsFilterInvoker]` — register a custom filter invocation handler +(`AsFilterCallback` and `AsListCallback` both extend the `@internal` base attribute `AsFlareCallback`.) + Attributes are in `src/DependencyInjection/Attribute/`, compiler passes in `src/DependencyInjection/Compiler/`. -**Event system** — Events, some with aliased dispatch for targeted listening (`flare.form.{name}.build`, etc.). All events are in `src/Event/`. Prefer events over overriding services for customization. +**Event system** — Events, some with aliased dispatch for targeted listening (`flare.form.{name}.build`, etc., implemented by the listeners in `src/EventListener/NamedDispatch/`). All events are in `src/Event/`. Prefer events over overriding services for customization. -**Registry pattern** — `FilterElementRegistry`, `ListTypeRegistry`, `FilterInvokerRegistry`, `ProjectorRegistry` map type names to implementations. +**Registry pattern** — Registries in `src/Registry/` map type names to implementations: `FilterElementRegistry`, `ListTypeRegistry`, `FilterInvokerRegistry`, `ProjectorRegistry`, `FilterCollectorRegistry`, `FlareCallbackRegistry`, `EngineModRegistry`. **Query safety** — `FilterQueryBuilder` (`src/Query/FilterQueryBuilder.php`) enforces parameterized queries. `TableAliasRegistry` (`src/Query/TableAliasRegistry.php`) manages table aliases and JOINs safely. @@ -61,13 +77,26 @@ Attributes are in `src/DependencyInjection/Attribute/`, compiler passes in `src/ * `make php ` — Runs PHP commands (e.g., `make php bin/console debug:container`) * `make composer ` — Runs Composer commands (e.g., `make composer install`) * `make phpstan` — Runs PHPStan static analysis (level 5) + * `make phpstan-pro` — Runs PHPStan Pro (web GUI, don't run as agent) + * `make semgrep-sec` — Runs the Semgrep security scan * `make docs-setup` — Sets up the Docusaurus environment in the `docs/` directory * `make docs-remove` — Safely removes the documentation worktree * `make help` — Lists all available make commands +* There is **no make target for Mago** (lint/format) — Mago runs in CI only. #### Static Analysis (PHPStan) * **Config:** `phpstan.neon` — level 5, analyses `src/` (with a number of excludes) * `src/Model/` is excluded because Contao models use magic `__get`/`__set` properties +* `src/Integration/Terminal42Languages/` is also excluded + +## Testing & CI + +* **There is currently no test suite**: no `tests/` directory, no `phpunit.xml`, no test CI workflow — even though PHPUnit is in `require-dev` and `tests/` is referenced in `autoload-dev` and `mago.toml`. Don't look for tests or invent a `make test` target. +* CI workflows in `.github/workflows/`: + * `phpstan.yaml` — PHPStan analysis + * `mago.yaml` — Mago lint (`--minimum-fail-level note`, PHP 8.2–8.5) + * `compatibility.yaml` — `composer validate` + dependency-resolution matrix (PHP 8.2–8.5 × Contao 4.13/5.x) + * `security.yaml` — `composer audit` + Semgrep, also on a weekly schedule ## Key Directories diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000..47dc3e3d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/contao/templates/content_element/flare_listview/list_only.html.twig b/contao/templates/content_element/flare_listview/list_only.html.twig index 39424be6..e28a5262 100644 --- a/contao/templates/content_element/flare_listview/list_only.html.twig +++ b/contao/templates/content_element/flare_listview/list_only.html.twig @@ -1,10 +1,12 @@ {% extends '@Contao/block_searchable.html5' %} +{% set flare_list = flare.createView %} + {% block content %} {% block list %} - {% for entry in flare.entries %} + {% for entry in flare_list.entries %}
{# (entry.headline is defined) ? (entry.headline|flare_fmt_headline_value|default('<' ~ loop.index ~ '>')) : #} #{{ entry.id }} {{ entry.title ?? (entry.email ?? entry.alias ?? ('<' ~ loop.index ~ '>')) }} diff --git a/src/DependencyInjection/Attribute/AsFilterInvoker.php b/src/DependencyInjection/Attribute/AsFilterInvoker.php index ab579593..3ec83e6e 100644 --- a/src/DependencyInjection/Attribute/AsFilterInvoker.php +++ b/src/DependencyInjection/Attribute/AsFilterInvoker.php @@ -4,6 +4,9 @@ namespace HeimrichHannot\FlareBundle\DependencyInjection\Attribute; +/** + * @deprecated Define filter invocation via the filter element's `__invoke` method instead. Removal pending for v0.2. + */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class AsFilterInvoker { diff --git a/src/Engine/Engine.php b/src/Engine/Engine.php index f01f8329..9641b6d1 100644 --- a/src/Engine/Engine.php +++ b/src/Engine/Engine.php @@ -56,7 +56,7 @@ public function createView(): ViewInterface /** * @api */ - public function addMod(string $modType, array $config): self + public function addMod(string $modType, array $config = []): self { $this->mods[] = [ 'type' => $modType, diff --git a/src/Engine/Mod/ModInterface.php b/src/Engine/Mod/ModInterface.php index 780f7e1f..2e8b90a4 100644 --- a/src/Engine/Mod/ModInterface.php +++ b/src/Engine/Mod/ModInterface.php @@ -6,14 +6,11 @@ use HeimrichHannot\FlareBundle\Engine\Engine; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; -use Symfony\Component\OptionsResolver\OptionsResolver; #[AutoconfigureTag('flare.engine_mod')] interface ModInterface { public static function getType(): string; - public function configureOptions(OptionsResolver $resolver): void; - public function apply(Engine $engine, array $options): void; } \ No newline at end of file diff --git a/src/Query/QueryHelperTrait.php b/src/Query/QueryHelperTrait.php index 8d1dba89..78f7b464 100644 --- a/src/Query/QueryHelperTrait.php +++ b/src/Query/QueryHelperTrait.php @@ -6,11 +6,6 @@ trait QueryHelperTrait { - public function column(string $column, string $alias = TableAliasRegistry::ALIAS_MAIN): string - { - return sprintf('%s.%s', $alias, $column); - } - public function makeJoinOn(string $alias1, string $col1, string $alias2, string $col2): string { return sprintf('%s.%s = %s.%s', $alias1, $col1, $alias2, $col2);