The README still framed the project as "Phase 5 / pre-v0.1.0" and the docs predated the v0.2.0 surface (typed BridgeOp, public service interfaces, port negotiation, pre-migration auto-backup, bridge:export, periodic auto-update, two new makers, qmltestrunner). Bring them in line with what's actually shipped, and add badges (release, license, PHP, Symfony, Qt, FrankenPHP, CI, platform) to the README so the status is legible at a glance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.7 KiB
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; macOS and Windows packaging are tracked under PLAN.md §13 Phases 4b/4c. 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.
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
- Phase 0 ✅ throwaway transport spike.
- Phase 1 ✅ framework skeleton, dev mode, single-instance lock, CI quality gate.
- Phase 2 ✅ reactive models, update semantics, headline maker.
- Phase 3 ✅ POC todo app, integration + snapshot tests.
- Phase 4a ✅ bundled mode, Linux AppImage, release CI, AppImageUpdate.
- Phase 5 ✅ DX polish — dev console, init script, hot-reload docs (shipped with v0.1.0).
- v0.2.0 ✅ post-v0.1 architecture audit + operations row: typed
BridgeOpenum, public service interfaces, port negotiation, pre-migration auto-backup,bridge:export, periodic auto-update check, two new makers (event,read-model),qmltestrunnerin CI. - Phase 4b/4c ⏳ macOS / Windows packaging.
- v1.0.0 ⏳ API stabilisation; pre-1.0 minor bumps may still break.
Tested platforms
| OS | Packaging | CI |
|---|---|---|
| Linux x86_64 | AppImage | Gitea Actions (every push) |
| macOS / Windows | not yet | — |
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.