# php-qml A framework for building native desktop applications with a Symfony / FrankenPHP backend and a Qt/QML frontend, packaged as a single distributable per OS. ## Status **Phase 5 / pre-v0.1.0.** Phases 0–4a are merged: working framework skeleton, reactive models, the headline `make:bridge:*` makers, a real POC (`examples/todo`), Linux AppImage packaging, AppImageUpdate auto-update, performance smoke harness, release CI on `v*` tags. Full design lives in [PLAN.md](PLAN.md) and the per-version log in [CHANGELOG.md](CHANGELOG.md). What's not done yet: macOS and Windows packaging (Phase 4b/4c). ## What it is php-qml lets a PHP developer write a desktop application 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 process owns the window, input, and rendering. - A bundled FrankenPHP child runs a Symfony application in worker mode. - They communicate over a local socket — HTTP for commands and queries, Mercure SSE for state push. The framework provides the lifecycle, transport, reactive models, and scaffolding so application code stays idiomatic on both sides. ## What it is not Not a PHP↔Qt language binding. It does not embed PHP into a Qt event loop and it does not generate Qt classes from PHP. The two languages run in separate processes; the bridge is a wire protocol, not an FFI layer. If you've watched php-gtk and php-qt go quiet, that is the failure mode this project deliberately avoids — the framework owns the boring parts (lifecycle, transport, conventions) so it doesn't depend on a single maintainer keeping a language binding alive. ## Tech stack - **Backend:** PHP 8.4+, Symfony 8, Doctrine ORM 3, FrankenPHP (worker mode), Mercure - **Frontend:** Qt 6.5+, QML, C++ plugin where required - **Build:** CMake, Composer - **CI:** Gitea Actions, Gitea Releases - **Packaging:** Linux AppImage today; macOS (`.app` + `.dmg`) and Windows (NSIS / MSIX) on the roadmap ## Quick start Prerequisites: Qt 6.5+ dev packages, CMake, gcc-c++, PHP 8.4+, Composer, [FrankenPHP](https://frankenphp.dev/) on PATH. ```bash git clone https://gitea.example//php-qml cd php-qml # 1) Scaffold a fresh app (auto-detects this checkout as the framework) ./bin/php-qml-init my-app # 2) Boot it cd my-app make doctor # bridge:doctor — readiness check make dev # FrankenPHP --watch + Qt host ``` `make dev` opens the Qt window, connection state flips to **Online**, and a Ping button round-trips through `/api/ping` and back via Mercure SSE. Add a reactive resource — entity + REST controller + QML snippet — with one maker: ```bash cd my-app/symfony bin/console make:bridge:resource Todo bin/console make:migration && bin/console doctrine:migrations:migrate -n ``` The QML side gets a `TodoList.qml` whose `ReactiveListModel` does an initial GET, subscribes to `app://model/todo`, and applies diffs as Mercure pushes them. There is no handwritten cross-side glue. For a non-trivial example with a multi-window test, crash-recovery test, and AppImage packaging, see [`examples/todo/`](examples/todo/README.md). ## Packaging (Linux) ```bash cd examples/todo FRANKENPHP=/path/to/frankenphp make appimage ./build/Todo-x86_64.AppImage ``` `make appimage` bundles Qt, the Symfony app, the FrankenPHP binary, and the AppImageUpdate sidecar into one `~150 MB` AppImage that auto-detects bundled mode (no `BRIDGE_URL` set), spawns its own FrankenPHP child, generates a per-session bearer token, and runs first-launch migrations. CI builds this on every `v*` tag and uploads to Gitea Releases with a `latest.json` appcast and zsync metadata for in-place updates. See [PLAN.md §13 Phase 4a](PLAN.md#phase-4a) for the full pipeline. ## Project structure ```text bin/ php-qml-init # bash scaffolder — copies the skeleton into a new project framework/ php/ # the Symfony bundle (php-qml/bridge) qml/ # the Qt module (PhpQml.Bridge): BackendConnection, ReactiveListModel, … skeleton/ # reference application — what php-qml-init copies examples/ todo/ # POC todo app exercising every primitive packaging/linux/ # build-appimage.sh + AppImageUpdate sidecar wiring .gitea/workflows/ # ci.yml (PR gate) + release.yml (v* tags) PLAN.md # design source of truth CHANGELOG.md # release log ``` See [PLAN.md §9](PLAN.md#9-project-layout) for the conceptual layout. ## Roadmap Six phases, each ending with something runnable. Detail in [PLAN.md §13](PLAN.md#13-roadmap-to-poc). - **Phase 0** ✅ throwaway spike — prove transport on Linux. - **Phase 1** ✅ framework skeleton, dev mode, single-instance lock, CI quality gate. - **Phase 2** ✅ reactive models, update semantics, headline maker (`make:bridge:resource`). - **Phase 3** ✅ POC todo application generated via the makers; testing infrastructure. - **Phase 4a** ✅ bundled mode, Linux AppImage, release CI, AppImageUpdate auto-update, performance smoke. - **Phase 4b/4c** ⏳ macOS and Windows packaging. - **Phase 5** 🚧 DX polish (in progress) — dev console, init script, hot-reload docs, v0.1.0 prep. ## Contributing Active development happens on the `dev` branch; `main` only carries release commits. Pull requests target `dev`. Quality gate that CI runs: ```bash cd framework/php && composer quality # PHPStan + cs-fixer (check) + PHPUnit cd examples/todo && make quality # adds qmllint + integration test ``` A dedicated `CONTRIBUTING.md` will arrive alongside Phase 5's wrap-up. ## Versioning [Semantic Versioning](https://semver.org/) — `MAJOR.MINOR.BUGFIX`. MAJOR for breaking changes, MINOR for backwards-compatible features, BUGFIX for backwards-compatible fixes. Pre-v1.0.0 minor bumps may break public API. ## License To be decided before v0.1.0 is tagged. The framework's own code will be permissively licensed; note that Qt is shipped under LGPL and that carries obligations for distributors — see [PLAN.md §12](PLAN.md#12-open-questions-and-risks) (Qt LGPL relinkability).