Files
php-qml/README.md
magdev 26a2b3771b Phase 5 sub-commit 4: release readiness — README + CHANGELOG + status line
README.md rewritten to reflect actual onboarding (clone → php-qml-init
→ make dev / make appimage) instead of the planning-stage placeholder.
Phase status checklist now reflects 0–4a green and 5 in progress.

CHANGELOG.md created at repo root following Keep-a-Changelog
conventions, with a v0.1.0 entry that summarises Phases 0–4a plus the
Phase 5 polish work (DevConsole, php-qml-init, editor configs,
hot-reload docs). Date is TBD; tagging is the user's call.

PLAN.md gains a Status banner so it's obvious at a glance which phase
the implementation tracks against the design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:30:08 +02:00

127 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 04a 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/<you>/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).