The Status note still pointed at the dead Phase-4b/4c framing; the Roadmap was a mix of legacy phase numbering and version numbers and didn't reflect that macOS/Windows/Flathub/Snap have been consolidated into a single v0.9.0 cross-platform packaging push (PLAN.md §13). Drop phases entirely, list each shipped/upcoming SemVer version, and pull v0.3.0 (i18n, persistent logs, cache warmup) and v0.9.0 forward so the roadmap matches what's actually planned. Add a 'What you get' section between 'What it is' and the 60-second tour with concrete numbers (bundle size, cold start, idle RSS) and the shipped capabilities (five makers, reactive models, supervisor hardening, self-update, DX tooling, CI surface) so the README has more substance than just an architecture description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
9.9 KiB
Markdown
126 lines
9.9 KiB
Markdown
# php-qml
|
||
|
||
A framework for native desktop applications with a **Symfony / FrankenPHP** backend and a **Qt / QML** frontend, packaged as a single distributable per OS.
|
||
|
||
[](https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.2.0)
|
||
[](LICENSE)
|
||
[](https://www.php.net/)
|
||
[](https://symfony.com/)
|
||
[](https://www.qt.io/)
|
||
[](https://frankenphp.dev/)
|
||
[](https://src.bundespruefstelle.ch/magdev/php-qml/actions/workflows/ci.yml)
|
||
[](docs/packaging-linux.md)
|
||
|
||
> **Status:** v0.2.0 (2026-05-03). Linux AppImage is the only packaged target through the v0.2.0 / v0.3.0 minors; macOS, Windows, Flathub and Snap all land together in [v0.9.0](PLAN.md#v090--cross-platform-packaging-release-candidate-milestone) as a single cross-platform packaging push. Pre-v1.0 SemVer permits API breaks on minor bumps — see [CHANGELOG.md](CHANGELOG.md).
|
||
|
||
---
|
||
|
||
## What it is
|
||
|
||
php-qml lets a PHP developer write a desktop app using ordinary Symfony on the backend and ordinary QML on the frontend. The two halves run as a **process pair** inside one bundled binary:
|
||
|
||
- A Qt/QML host owns the window, input, and rendering.
|
||
- A bundled FrankenPHP child runs a Symfony app in worker mode.
|
||
- They communicate over a local socket — HTTP for commands and queries, Mercure SSE for state push.
|
||
|
||
It is **not** a PHP↔Qt language binding — the languages run in separate processes; the bridge is a wire protocol, not an FFI layer. That deliberately avoids the failure mode that left php-gtk and php-qt unmaintained.
|
||
|
||
## What you get
|
||
|
||
- **One ~150 MB AppImage** bundling Qt, the Symfony app, FrankenPHP, and the AppImageUpdate sidecar. Cold start ≤ 2 s on bare metal (≤ 4 s on shared CI runners), idle RSS ≤ 200 MB. Gates enforced in CI on every release tag.
|
||
- **Five `make:bridge:*` makers** covering CRUD, non-CRUD commands, domain events, query-only read-models, and second windows. The headline `make:bridge:resource` generates entity + REST controller + `ReactiveListModel`-bound QML in one command, with optional `--with-dto` for `#[MapRequestPayload]` + RFC 7807 validation.
|
||
- **Reactive models out of the box.** `ReactiveListModel` / `ReactiveObject` do the initial GET, subscribe to Mercure, apply optimistic mutations, and reconcile via `Idempotency-Key` ↔ `correlationKey` round-tripping — no handwritten cross-side glue.
|
||
- **Production-grade bundled-mode supervisor.** Per-session bearer + JWT secrets (rotated on every restart), pre-migration SQLite auto-backup, runtime-negotiated TCP port (no two installed apps collide), `prctl(PR_SET_PDEATHSIG)` so a host crash takes the child with it.
|
||
- **Self-update** via embedded `zsync` (typical delta 10–20 MB). Auto-checks on launch and every 6 h; the install step is always user-driven, never auto-restart.
|
||
- **Opt-in DX**: `bridge:doctor` readiness probe, `bridge:export` database backup, `DevConsole` QML in-window log viewer (Ctrl+backtick), single-instance lock with launch-arg forwarding (file-association friendly), shipped `.vscode/` + `.idea/` configs.
|
||
- **Quality gate on every push**: PHPStan + php-cs-fixer + PHPUnit + qmllint + `qmltestrunner` + an HTTP/SSE round-trip integration test + a bundled-supervisor smoke test + `perfsmoke` against the budgets above.
|
||
|
||
## 60-second tour
|
||
|
||
```bash
|
||
git clone https://src.bundespruefstelle.ch/magdev/php-qml && cd php-qml
|
||
|
||
# Scaffold a fresh app
|
||
./bin/php-qml-init my-app
|
||
|
||
# Run it
|
||
cd my-app
|
||
make doctor # readiness check
|
||
make dev # FrankenPHP --watch + Qt host
|
||
```
|
||
|
||
Add a reactive resource (entity + REST controller + QML snippet) with one maker:
|
||
|
||
```bash
|
||
cd my-app/symfony
|
||
bin/console make:bridge:resource Todo # add --with-dto for #[MapRequestPayload] + RFC 7807 errors
|
||
bin/console make:migration && bin/console doctrine:migrations:migrate -n
|
||
```
|
||
|
||
`make dev` opens the Qt window, connection state flips to **Online**, and the generated `TodoList.qml` shows a list whose `ReactiveListModel` is auto-subscribed to `app://model/todo` over Mercure. There is no handwritten cross-side glue.
|
||
|
||
The maker family covers the four common shapes: [`make:bridge:resource`](docs/makers.md#makebridgeresource) (CRUD), [`make:bridge:command`](docs/makers.md#makebridgecommand) (non-CRUD action), [`make:bridge:event`](docs/makers.md#makebridgeevent) (domain event → QML signal), [`make:bridge:read-model`](docs/makers.md#makebridgeread-model) (query-only projection), and [`make:bridge:window`](docs/makers.md#makebridgewindow) (second window).
|
||
|
||
For a non-trivial app with a multi-window test, crash-recovery test, and AppImage packaging, see [`examples/todo/`](examples/todo/README.md).
|
||
|
||
## Documentation
|
||
|
||
The full developer documentation lives under [`docs/`](docs/README.md):
|
||
|
||
- **[Getting started](docs/getting-started.md)** — prerequisites by distro, first project, troubleshooting.
|
||
- **[Architecture](docs/architecture.md)** — process pair, transport, dev vs bundled mode.
|
||
- **[Update semantics](docs/update-semantics.md)** — connection state machine, optimistic mutations, idempotency.
|
||
- **[Reactive models](docs/reactive-models.md)** — `ReactiveListModel`, `ReactiveObject`, Mercure dual-publish.
|
||
- **[Makers](docs/makers.md)** — `make:bridge:resource` / `command` / `event` / `read-model` / `window`.
|
||
- **[Dev workflow](docs/dev-workflow.md)** — hot reload, dev console, editor setup, `bridge:doctor`, `make qmltest`.
|
||
- **[Bundled mode](docs/bundled-mode.md)** — supervisor, per-session secret rotation, port negotiation, pre-migration auto-backup, first-launch migrations.
|
||
- **[Linux packaging](docs/packaging-linux.md)** — `make appimage`, auto-update (launch + 6h poll), performance budgets.
|
||
- **[Native dialogs](docs/native-dialogs.md)** — file pickers, message boxes, system tray; the QML/PHP boundary.
|
||
- **[Configuration reference](docs/configuration.md)** — env vars, CLI flags.
|
||
- **[QML API reference](docs/qml-api.md)** / **[PHP API reference](docs/php-api.md)** — singletons, components, attributes, services, interfaces.
|
||
|
||
Design rationale and roadmap live in [PLAN.md](PLAN.md). User-facing changes per release are in [CHANGELOG.md](CHANGELOG.md).
|
||
|
||
## Tech stack
|
||
|
||
PHP 8.4+ · Symfony 8 · Doctrine ORM 3 · FrankenPHP 1.12+ (worker mode) · Mercure · Qt 6.5+ · CMake · Composer · Gitea Actions
|
||
|
||
## Roadmap
|
||
|
||
The original Phase 0–5 POC roadmap shipped as v0.1.0 on 2026-05-03. From there on, work is organised by SemVer version (see [PLAN.md §13](PLAN.md#13-versions) for the full per-version breakdown).
|
||
|
||
- **v0.1.0** ✅ first public preview — process-pair architecture, reactive models, three headline makers, bundled mode, Linux AppImage, AppImageUpdate, release CI, DX polish (dev console, `php-qml-init`, editor configs).
|
||
- **v0.1.1** ✅ shakedown follow-ups — `/healthz` deep-load canary, bundled-supervisor integration test, skeleton AppImage parity, cache-wipe on bundled launch.
|
||
- **v0.1.2** ✅ post-shakedown audit — clean child shutdown via `aboutToQuit`, configurable `bridge.qml_path`, `SessionAuthenticator` problem+json on the entry-point path, `CorrelationKeyListener` sub-request guard.
|
||
- **v0.2.0** ✅ public-API surface (`BridgeOp` enum, `PublisherInterface` / `ModelPublisherInterface` / `CorrelationContextInterface`, `BridgeBundleInfo`), port negotiation, pre-migration auto-backup, `bridge:export`, periodic auto-update check, `make:bridge:event` + `make:bridge:read-model` makers, `--with-dto` opt-in, `qmltestrunner` in CI.
|
||
- **v0.3.0** ⏳ later minor — i18n bridge (Symfony Translator + Qt Translator with shared locale switch), persistent log files + rotation, build-time Symfony cache warmup (requires `kernel.project_dir` virtualisation, hence its own minor).
|
||
- **v0.9.0** ⏳ cross-platform packaging release-candidate milestone — macOS (`.app` + Sparkle 2 + notarisation), Windows (NSIS + WinSparkle + Authenticode), Flathub + Snap, multi-arch (Linux ARM64, Windows ARM, macOS universal), composer `create-project php-qml/skeleton`, opt-in telemetry + crash reporting. Held until one push because the cert / runner / notarisation prerequisites overlap.
|
||
- **v1.0.0** ⏳ API stabilisation — auth model finalised, AppImage relinkability documented end-to-end, security model audited. Pre-1.0 minor bumps may still break public API.
|
||
|
||
## Tested platforms
|
||
|
||
| OS | Packaging | CI |
|
||
| --------------- | --------- | -------------------------- |
|
||
| Linux x86_64 | AppImage | Gitea Actions (every push) |
|
||
| macOS / Windows | v0.9.0 | — |
|
||
|
||
Performance gates (`tests/perfsmoke.sh`) enforced on every release tag: bundle ≤ 200 MB, cold start ≤ 2 s (4 s on shared CI), idle RSS ≤ 200 MB. See [docs/packaging-linux.md §performance smoke](docs/packaging-linux.md#performance-smoke).
|
||
|
||
## Contributing
|
||
|
||
Active development happens on the `dev` branch; `main` only carries release commits. Pull requests target `dev`.
|
||
|
||
```bash
|
||
cd framework/php && composer quality # PHPStan + cs-fixer + PHPUnit
|
||
cd framework/skeleton && make qmltest # qmltestrunner unit tests (Quick Test)
|
||
cd examples/todo && make quality # adds qmllint + integration test
|
||
```
|
||
|
||
## Versioning
|
||
|
||
[Semantic Versioning](https://semver.org/) — `MAJOR.MINOR.BUGFIX`. Pre-v1.0.0, minor bumps may break public API; bugfix bumps don't.
|
||
|
||
## License
|
||
|
||
[**LGPL-3.0-or-later**](LICENSE) — chosen to align with Qt 6's LGPLv3 licensing. The bundled AppImage honours the relinkability obligations (Qt libs are shipped as separate `.so`s, not statically linked); see [PLAN.md §12](PLAN.md#12-open-questions-and-risks) for the full rationale.
|