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>
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.
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 as a single cross-platform packaging push. Pre-v1.0 SemVer permits API breaks on minor bumps — see 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 headlinemake:bridge:resourcegenerates entity + REST controller +ReactiveListModel-bound QML in one command, with optional--with-dtofor#[MapRequestPayload]+ RFC 7807 validation. - Reactive models out of the box.
ReactiveListModel/ReactiveObjectdo the initial GET, subscribe to Mercure, apply optimistic mutations, and reconcile viaIdempotency-Key↔correlationKeyround-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:doctorreadiness probe,bridge:exportdatabase backup,DevConsoleQML 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 +perfsmokeagainst the budgets above.
60-second tour
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:
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 (CRUD), make:bridge:command (non-CRUD action), make:bridge:event (domain event → QML signal), make:bridge:read-model (query-only projection), and make:bridge:window (second window).
For a non-trivial app with a multi-window test, crash-recovery test, and AppImage packaging, see examples/todo/.
Documentation
The full developer documentation lives under docs/:
- Getting started — prerequisites by distro, first project, troubleshooting.
- Architecture — process pair, transport, dev vs bundled mode.
- Update semantics — connection state machine, optimistic mutations, idempotency.
- Reactive models —
ReactiveListModel,ReactiveObject, Mercure dual-publish. - Makers —
make:bridge:resource/command/event/read-model/window. - Dev workflow — hot reload, dev console, editor setup,
bridge:doctor,make qmltest. - Bundled mode — supervisor, per-session secret rotation, port negotiation, pre-migration auto-backup, first-launch migrations.
- Linux packaging —
make appimage, auto-update (launch + 6h poll), performance budgets. - Native dialogs — file pickers, message boxes, system tray; the QML/PHP boundary.
- Configuration reference — env vars, CLI flags.
- QML API reference / PHP API reference — singletons, components, attributes, services, interfaces.
Design rationale and roadmap live in PLAN.md. User-facing changes per release are in 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 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 —
/healthzdeep-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, configurablebridge.qml_path,SessionAuthenticatorproblem+json on the entry-point path,CorrelationKeyListenersub-request guard. - v0.2.0 ✅ public-API surface (
BridgeOpenum,PublisherInterface/ModelPublisherInterface/CorrelationContextInterface,BridgeBundleInfo), port negotiation, pre-migration auto-backup,bridge:export, periodic auto-update check,make:bridge:event+make:bridge:read-modelmakers,--with-dtoopt-in,qmltestrunnerin 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_dirvirtualisation, 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), composercreate-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.
Contributing
Active development happens on the dev branch; main only carries release commits. Pull requests target dev.
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 — MAJOR.MINOR.BUGFIX. Pre-v1.0.0, minor bumps may break public API; bugfix bumps don't.
License
LGPL-3.0-or-later — chosen to align with Qt 6's LGPLv3 licensing. The bundled AppImage honours the relinkability obligations (Qt libs are shipped as separate .sos, not statically linked); see PLAN.md §12 for the full rationale.