Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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).
Expand All @@ -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
Expand All @@ -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.

Expand All @@ -61,13 +77,26 @@ Attributes are in `src/DependencyInjection/Attribute/`, compiler passes in `src/
* `make php <args>` — Runs PHP commands (e.g., `make php bin/console debug:container`)
* `make composer <args>` — 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

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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 %}
<details>
{# (entry.headline is defined) ? (entry.headline|flare_fmt_headline_value|default('<' ~ loop.index ~ '>')) : #}
<summary>#{{ entry.id }} {{ entry.title ?? (entry.email ?? entry.alias ?? ('<' ~ loop.index ~ '>')) }}</summary>
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/Attribute/AsFilterInvoker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
2 changes: 1 addition & 1 deletion src/Engine/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 0 additions & 3 deletions src/Engine/Mod/ModInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
5 changes: 0 additions & 5 deletions src/Query/QueryHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading