Implements PLAN.md §8's fourth makers-table row. Read-models are
server-side projections — joined fetches, aggregates, denormalised
views — that QML reads without going through a writable
#[BridgeResource]. The maker emits:
- src/ReadModel/<Name>ReadModel.php — query service stub injecting
EntityManagerInterface; user fills query() with DQL / QueryBuilder
/ raw SQL as fits.
- src/Controller/<Name>Controller.php — single GET handler at
/api/<kebab-plural>, just normalises the read-model output to JSON.
- {qml_path}/<Name>List.qml — ReactiveListModel bound to the route,
deliberately no Mercure topic.
The "no topic" choice is the design call worth documenting: read-models
are queries, not reactive resources, and pretending otherwise would
either auto-publish stale aggregates on every entity change or require
the user to invent invalidation logic in the listener. Better: pair
the read-model with `make:bridge:event` and call refresh() from the
QML event-handler when the underlying data really changes.
Naming convention: kebab-PLURAL routes (`/api/todo-summaries`) for
consistency with REST list semantics; resource path stays singular
under `src/ReadModel/`.
Wired into services.yaml's when@dev block. Three new snapshot
baselines (TodoSummaryReadModel.php / TodoSummaryController.php /
TodoSummaryList.qml) plus runner extension. All 14 maker outputs
verify on the committed state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.9 KiB
YAML
52 lines
1.9 KiB
YAML
services:
|
|
_defaults:
|
|
autowire: true
|
|
autoconfigure: true
|
|
|
|
PhpQml\Bridge\:
|
|
resource: '../src/'
|
|
exclude:
|
|
- '../src/BridgeBundle.php'
|
|
|
|
PhpQml\Bridge\SessionAuthenticator:
|
|
arguments:
|
|
$expectedToken: '%env(default::BRIDGE_TOKEN)%'
|
|
|
|
# Maker classes extend symfony/maker-bundle's AbstractMaker, which is a
|
|
# require-dev dependency. In `composer install --no-dev` builds (the
|
|
# staging-symfony tree the AppImage is assembled from) AbstractMaker is
|
|
# absent: PHP fails to autoload BridgeResourceMaker etc., so the glob
|
|
# above silently drops them — that's fine. But a top-level explicit
|
|
# `services.PhpQml\Bridge\Maker\BridgeResourceMaker:` block forces
|
|
# ResolveClassPass to load the class regardless of dev/prod, which then
|
|
# crashes the prod container compile. Scope the qml_path injection to
|
|
# `when@dev:` so prod builds never touch these definitions.
|
|
when@dev:
|
|
services:
|
|
# _defaults must be repeated here — `when@<env>` opens a fresh
|
|
# services block, so the top-level autowire/autoconfigure don't
|
|
# carry over. Without autoconfigure the explicit definitions
|
|
# below would lose maker-bundle's `maker.command` tag, and
|
|
# `make:bridge:resource` would silently disappear from the
|
|
# console while `make:bridge:command` (registered by the glob,
|
|
# no override) keeps working.
|
|
_defaults:
|
|
autowire: true
|
|
autoconfigure: true
|
|
|
|
PhpQml\Bridge\Maker\BridgeResourceMaker:
|
|
arguments:
|
|
$qmlPath: '%bridge.qml_path%'
|
|
|
|
PhpQml\Bridge\Maker\BridgeWindowMaker:
|
|
arguments:
|
|
$qmlPath: '%bridge.qml_path%'
|
|
|
|
PhpQml\Bridge\Maker\BridgeEventMaker:
|
|
arguments:
|
|
$qmlPath: '%bridge.qml_path%'
|
|
|
|
PhpQml\Bridge\Maker\BridgeReadModelMaker:
|
|
arguments:
|
|
$qmlPath: '%bridge.qml_path%'
|