The framework's reference application: a minimal Symfony backend plus a Qt/QML host, wired together exactly the way `php-qml/bridge` is intended to be used. Use this as a starting point or copy parts into your own project.
# 3) Use the generated TodoList.qml from your QML window.
```
That's it. The bundle's Doctrine subscriber automatically dual-publishes
`postPersist`/`postUpdate`/`postRemove` events to:
-`app://model/todo` (collection topic, watched by `ReactiveListModel`)
-`app://model/todo/{id}` (entity topic, watched by `ReactiveObject` — Phase 3)
The `ReactiveListModel` in `TodoList.qml` does an initial `GET /api/todos`, subscribes to the collection topic, and applies diffs to its rows as they arrive. Adding `--int-id` switches the maker to auto-incrementing integer IDs.
### Verifying end-to-end
With `make dev` running, post a todo from another terminal and watch it appear:
```bash
curl -X POST http://127.0.0.1:8765/api/todos \
-H 'Authorization: Bearer devtoken' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: my-key-1' \
-d '{"title":"buy milk","done":false}'
```
The Mercure SSE stream receives a `correlationKey: my-key-1` envelope which the Qt host's `ReactiveListModel` matches against any in-flight optimistic mutation (PLAN.md §5).
Both halves of the app reload without re-running `make dev`.
### PHP-side (Symfony / FrankenPHP)
`make dev` runs `frankenphp run --watch` (see `Caddyfile` and `scripts/dev.sh`). FrankenPHP rebuilds the worker on any change under `symfony/` — controllers, services, entities, templates, configuration. Just save the file; the next request through the Qt host hits the new code. There is no opcache to clear and no service to restart.
If you change something Doctrine-mapped, run a fresh migration in another terminal:
```bash
cd symfony
bin/console make:migration
bin/console doctrine:migrations:migrate -n
```
The Qt host stays up across all of this.
### QML-side
The Qt host loads QML from a compiled-in resource bundle, so saving a `.qml` file does **not** flip the running window automatically. Three workflows that do:
- **Qt Creator → File → Reload** (or `Ctrl+R` with focus on a QML file). Rebuilds the QML cache and reloads the window in place.
- **`qmlls` live preview** — the QML language server bundled with Qt 6.5+ runs a live preview connected to your editor (VSCode + the Qt extension, neovim, helix). Edits show up instantly in the preview window without rebuilding.
- **Run from source** — start the host with `QT_QUICK_CONTROLS_CONF=` and `QML_IMPORT_TRACE=1` set, and pass `-DQT_QML_DEBUG` so the running engine accepts a hot-reload connection from Qt Creator. PLAN.md §6 captures the long-term plan to gate this behind `BRIDGE_DEV=1`.
For most edits, Qt Creator's *Reload* is the lowest-friction option. The `.qmlls.ini` file (auto-generated when `qmlls` first runs) configures completion + live preview against this project's QML import paths.
### Editor configs
Both `.vscode/` and `.idea/runConfigurations/` ship with the skeleton.
VSCode (`.vscode/launch.json`):
- **Listen for Xdebug** — attaches the debugger on port 9003 once you set `XDEBUG_MODE=debug` for the FrankenPHP child (e.g. `XDEBUG_MODE=debug make dev`).
- **Run skeleton (Qt host)** — gdb-launches the built binary with `BRIDGE_URL=http://127.0.0.1:8765` so it talks to the dev mode FrankenPHP started elsewhere by `make dev`.
- **Compound: Dev: Xdebug + Qt host** — runs both at once.
PhpStorm (`.idea/runConfigurations/`): `make dev`, `make doctor`, `make quality` shell run configs. PHP debugging is via the toolbar's **Start Listening for PHP Debug Connections** toggle (PhpStorm's Xdebug listener is global, not per-project).
### Dev console
`Ctrl+`` toggles an in-window console showing the bundled FrankenPHP child's stdout + stderr (PLAN.md §8). It's a passive ring buffer (~500 lines) — opening it has no IPC cost. Use it when you don't have a separate terminal to read the dev log.
scripts/dev.sh # process-group teardown for FrankenPHP + Qt host
symfony/ # the Symfony app (composer project)
config/
src/Entity/ # filled by `make:bridge:resource`
src/Controller/ # filled by `make:bridge:resource`
public/index.php
bin/console
qml/ # the Qt host
CMakeLists.txt
main.cpp
Main.qml
<Name>List.qml # filled by `make:bridge:resource` (relative to symfony/)
```
## Where to go next
- The framework code lives one level up: [`framework/php`](../php) (the bundle) and [`framework/qml`](../qml) (the Qt module). Both are linked into this skeleton via Composer's path repository / CMake's `add_subdirectory`, so edits there are picked up live.
- The full design story is in [`PLAN.md`](../../PLAN.md).
- Phase 3 wires this skeleton into a real POC todo application with a multi-window test and packaging-CI dry runs.