NativePHP for Symfony
GitHub

Documentation

Start here. This directory is the documentation for using NativePHP for Symfony; the Markdown files at the repository root are mostly analysis, specification, or a record of how the port was built.

Using it

Getting started — desktopcomposer require to a window on screen, including the two things that are not automatic
Getting started — mobileBoth render paths, and what "verified by tests, not by a device" means
Desktop API referenceWindows, menus, dialogs, notifications, clipboard, settings, child processes, Messenger, events
Mobile API referenceThe 16 bridge groups, and the native-UI element/component/style layers
RecipesMulti-window apps, a Messenger worker, typed listeners, a stateful native screen, packaging
TestingFakeRuntime, RuntimeExpectations, RuntimeEventSimulator, FakeBridge
TroubleshootingSymptom → cause → fix. Read this first when something is silently wrong, which is this runtime's usual failure mode

A worked example

../demo/ is a Symfony 8 application using both bundles: a bootstrapper, a multi-window inspector, a notes CRUD over the settings store, a Messenger-style job page, a menu with routed events, a native-UI counter screen, and a native_publish() Twig function the app owns rather than the bundle. Read it after the getting-started page for this project's idea of idiomatic use. It is newer than these docs; where it disagrees, it is probably right.

Reference — the protocol, not the guide

Current and authoritative. Consult these when a wrapper does not cover what you need, or when you are checking a payload shape.

../CONTRACT.mdThe desktop wire protocol: all 118 endpoints with request and response shapes, all 46 events, the environment contract, and the seven things the runtime requires of any PHP app
../NATIVE-UI-CONTRACT.mdThe mobile native-UI wire format: node shapes, content hashes, id derivation, the callback protocol, the style vocabulary
../bundle/README.mdDesktop package overview
../mobile-bundle/README.mdMobile package overview

The two package READMEs are summaries and lag the code slightly — see known drift below. Where they disagree with these docs, these docs were written against the source.

Understanding it

../ARCHITECTURE.mdHow desktop and mobile differ, what the two bundles share and deliberately do not, the three verification techniques, and a collected list of the traps. The best single document if you read only one

Working on the port

Not needed to build an application with it.

../PLAN.mdWhat NativePHP is, where Laravel leaks, and the milestone roadmap
../ANALYSIS.mdThe deep dive: boot sequence, per-directory port map with LOC, the eight Laravel-isms with line numbers, the Symfony-specific design decisions, upstream bugs found
../MOBILE-ANALYSIS.mdWhy mobile splits in two, and a correction to an earlier conclusion that was wrong
../upstream-patches/README.mdEleven patches. Four have landed in NativePHP/desktop's main; 0002–0003 are still open there and 0008–0011 are open on mobile-air. Patch 0001 is the manifest change that would make the runtime framework-agnostic and retire the patcher. None of them the bundle waits on — RuntimePatcher carries every fix into your copy, and reports the ones upstream has since fixed itself
../spike/README.mdThe reproduction harness — a container with PHP, Node, Electron and Xvfb that boots the runtime headlessly and screenshots it

Historical record

These are milestone write-ups. They are accurate about what was true when they were written, and they are where the evidence for the current behaviour lives — but they are not the place to learn how to use anything.

../SPIKE-RESULTS.mdM1: proving a Symfony app can run under the runtime at all. Five findings, including APP_ENV=local and the resize that emits no event
../M2-RESULTS.mdM2: the bundle. Seven findings, including sendStatus sending "OK", the NaN zoom factor, and the dot-path settings key
../M3-RESULTS.mdM3: the build pipeline, and a packaged app that was built and run. Four findings, including the APP_SECRET collision and no opcache

Every "worth knowing" in the pages above traces back to one of these three.

Known drift in older documents

Everything the first pass of these docs found wrong has since been fixed in code, not papered over — the list is kept because how each was found is the useful part.

Was wrongNow
native_publish() was documented and never registeredRegistered; returns void, so {% do native_publish(...) %} is the spelling
Parsed styles never reached the wire — the documented ->layout($parser->parse(...)) put camelCase keys where the renderers read snake_caseStyleApplier dispatches them, or use the class option
Element props were camelCase (fontSize), text_align was a string, font_size an intAll match the wire; a test now compares an element with props, which is how these surfaced
NativeScreenResponder typed its renderer non-nullable while DI wired it nullOnInvalid — a TypeError for any app enabling the bundleNullable, with a guard naming the interface to implement
The install flow predated native:manifestDocumented, and patching is skipped when the runtime is manifest-aware
Test counts contradicted each other across three READMEsRead from the suites
Nothing implemented ScreenRendererInterface, so a #[NativeScreen] component needed application glue — the two halves of the native-UI path did not join upComponentScreenRenderer ships, aliased to the interface; override the alias for a different renderer
The routes import was a manual step, and forgetting it gave an app that boots and shows nothingnative:install writes it, and warns rather than guessing when there is no config/routes/
Five runtime defects — four filed as NativePHP/desktop #137–#140 and unreviewed, one found here — were left to upstream's release schedule, which on discussion #504 has meant eighteen months of silenceRuntimePatcher carries all five into the copy native:install writes. Non-strict hunks, so a target that moves is reported, not fatal — which is what now happens for #137 and #140, both since merged upstream
An application firewall silently denied the runtime's own callbacks — access_control: ^/ makes POST /_native/api/booted a 401, which the runtime discards, giving a window that opens and does nothing. Laravel is not exposed to this: there the two routes sit outside the app's middleware groupsRuntimeRoutesAccessMap decorates security.access_map so the paths carry no attributes inside the runtime only — a bundle cannot fix it as config, since both security.firewalls and security.access_control are single-source. Found by diffing our route registration against upstream's routes/api.php
native:build crashed part-way through on any app containing a self-referential symlink (public/storage -> ..), with a FileNotFoundException naming a path forty levels deepThe staging walk remembers real directories and refuses to enter one twice
native:doctor reported ✗ does not route for both runtime endpoints on a correctly wired application, on any symfony/routing below 7.4 — the one failure a diagnostic must never have, since the reader then rewrites a routes file that was already rightIt matched a Request::create($path, 'POST') without setting the method on the router's context, and UrlMatcher only began taking it from the request in 7.4 and 8.1. Found by adding composer update --prefer-lowest to CI before publishing: the declared range was a promise nobody had checked, and it was false in three places
ProjectPath::absolute() answered two different things across the declared range while its own docblock claimed one — {project}/C:\dev on new Symfony, {project}/C:/dev on oldPath::join() stopped rewriting backslashes in symfony/filesystem 7.4 and 8.1, and the method had been leaning on it to normalise. It normalises explicitly now, in both bundles, so the answer is not the resolver's business
The demo itself did layout: $parser->parse(...) on two pages — the very mistake row three above recordsBoth go through StyleApplier now; found by reading a published frame, not by a test

Two caveats that are not drift and will not be fixed:

  • Mobile is verified by tests, not on a device. There is no Xcode or Android SDK in the environment this was built in. Desktop's claims are backed by a running packaged app; mobile's are not, and no amount of test coverage changes that.
  • SPIKE-RESULTS.md, M2-RESULTS.md and M3-RESULTS.md are a journal. They record what was true at a milestone, including conclusions later corrected. Read them for the reasoning, not the API.