MARY.WIN

TALK

Design Patterns
in NativePHP

Design patterns under pressure in the NativePHP v4 render cycle

The shape of the nine.

Nine design patterns under pressure inside the NativePHP v4 render cycle — where PHP drives SwiftUI and Compose with no webview and no JSON bridge. One request, followed all the way — pipeline order is the argument.

01 — Front controller entry

One entry point that routes every request to the object that handles it.

verified PHP classical UML · Gang of Four adjacent · and already in your muscle memory

dispatches to
Client
one request
FrontController
- handlers: map
+ handle(request)
«abstract»Handler
+ render(): View
HomeHandler
+ render()
TabsHandler
+ render()
Many handlers, one door. The controller knows how to dispatch — nothing else.
02 — Interpreter render

A DSL with an evaluation step — Tailwind-looking syntax, but nothing CSS ever touches it.

verified PHP classical UML · Gang of Four, chapter 5

interpret(ctx) evaluated to a value
Context
the template
«abstract»AbstractExpression
+ interpret(ctx)
Element
an object
TerminalExpression
a value: text-7xl
→ fontSize(72)
Nonterminal
holds expressions:
native:column
The grammar is the tag set. The evaluation happens before anything crosses the boundary.
03 — Composite render

Elements form a tree — screen → column → text + button. Higher-level components you compose flatten; only primitives appear in the published tree.

verified PHP classical UML · the self-referential one

children 0..*
Client
your render()
«abstract»Component
+ render()
Leaf
no children
+ render()
Composite
- children: Component[]
+ add(c) · + render()
A composite holds components — including other composites. One uniform node type, arbitrarily deep.
04 — Command render

@press="refresh" is registered and replaced with a stable callback ID. Nodes reference handlers by ID; the native event carries the ID back; PHP resolves it to your method.

verified PHP + documented ID resolution classical UML · Gang of Four, and the one that had no alternative

holds an ID, not a reference receiver
Invoker
- token: int
+ fire(token)
«abstract»Command
+ execute()
ConcreteCommand
- receiver: Receiver
+ execute() { receiver.action() }
Receiver
your component
+ action()
The invoker can be on the other side of a process boundary, because all it needs to send back is the token.
05 — Structural sharing publish

If only part of the tree changed, unchanged subtrees become tiny reuse markers instead of being re-encoded.

documented architecture · beta Flyweight-adjacent — not GoF

re-encoded reuse marker both point at one subtree
Frame n
the text changed
Frame n+1
nothing changed
full node records
type | layout | style…
marker: reuse(k)
a few bytes
shared subtree
key: 'rows-container'
encoded once
spliced thereafter
Not Flyweight: nothing is interned for memory. It’s the encode step that’s being saved.
06 — Producer / consumer publish

The Element Runtime writes each element into shared memory as a node, atomically bumps the frame version, and wakes the native reader — all on the PHP thread, off the UI thread.

documented architecture · beta not in the book — honest name · there is no PHP to show you here, and that is itself the point

writes nodes wakes reader events come back the other way (pattern 4)
Producer
the PHP thread
+ publish(tree)
Shared buffer
- nodes: byte[]
- version: atomic int
one memory region
no locks on the hot path
Consumer
native reader thread
+ read(version)
+ mount()
You bump a version and move on. Nobody waits for anybody.
07 — Reconciliation mount

A dedicated reader thread decodes the nodes and diffs against the tree it rendered last time. Reuse markers are spliced from the previous tree without decoding. SwiftUI and Compose re-render exactly the views whose nodes changed.

documented architecture · beta lineage: virtual DOM — not GoF · this is why a running animation survives a re-render

minimal
previous Node Tree
what is on screen
right now
incoming Frame
nodes + markers,
just published
diff
markers → splice, don’t decode
nodes → compare field by field
native view tree
column — untouched
text — re-rendered
button — untouched
One node changed. One view re-rendered. The drag in progress two nodes over never noticed.
08 — Bridge mount

One abstraction — the node — against two independently varying implementations. Flexbox values on each node drive a native Layout implementation per platform.

verified PHP abstraction + documented implementations the word “bridge”, with two arrows pointing at different things — and only one of them is a pattern

CORRECTION — If you learned NativePHP v3, “bridge” was the name of an IPC channel. Forget that one. The Gang of Four Bridge is here instead.
has an implementor the node crosses once… …and forks on the far side
«abstraction»Node
- layout: flexbox values
+ mount()
«implementor»Layout
+ apply(layout)
SwiftUILayout
iOS — compiled,
not PHP
ComposeLayout
Android — compiled,
not PHP
Abstraction and implementation vary independently — that is the definition, not an analogy.
09 — Proxy resolution

v3: the PHP object was a proxy for native state. v4: native owns the value; PHP holds a handle. The pattern didn’t change — the direction of ownership did.

verified PHP classical UML · and then the same diagram with one arrow reversed

the book’s picture

holds a reference
Subject
+ get() / + set(v)
RealSubject
owns the value
Proxy
stands in for it

the same picture, in v4

holds a handle updates at frame rate
PHP
SharedValue
— the handle
native
owns the value
gestures write it
on the UI thread
at the display’s
full frame rate
Same roles. Same collaboration. The arrow points the other way.

Every one of these is a forced move at a boundary — and the cycle resolves into something you can watch happen.

Check my work.

Every row below is a claim from the talk. Documented rows quote the NativePHP v4 docs verbatim — the exact line is shown. Interpretation rows are my own reading: the Gang-of-Four pattern names are mine to argue for, not the docs'. Quotes are unedited; links go to the page they're pulled from.

CLAIM × DOC LINE
Claim in the talk Type Source Exact line from the docs
The PHP runtime is embedded inside the app process; it talks to native over shared memory, not sockets. Documented Embedded PHP "PHP ships inside your app as a library"; "there's no FastCGI, no sockets, no per-request process to spawn"; "shared memory only works when both sides share a process."
The Element Runtime is a PHP extension — native code compiled into libphp itself. Documented Embedded PHP Glossary "The Element Runtime is a PHP extension — native code compiled into libphp itself, alongside the engine."
It ships as prebuilt binaries pinned to each release. Documented Embedded PHP "every release of nativephp/mobile pins exact binary builds… produced and shipped together, from matching sources."
The pipeline has three phases: Render, Publish, Mount. Documented Render, Publish, and Mount Doc page titled "Render, Publish, and Mount," describing all three stages.
Render: PHP builds an Element Tree. Documented Render, Publish, and Mount Glossary "PHP builds an Element Tree describing what the screen should look like."
Each native: tag emits an Element — a plain PHP object. Documented Render, Publish, and Mount Glossary "Blade compiles the template, and each native: tag emits an Element: a plain PHP object describing one piece of UI."
Utility classes like p-4 / text-2xl are parsed into layout and style values in PHP, not on the native side. Documented Render, Publish, and Mount "Utility classes like p-4 and text-2xl are parsed into concrete layout and style values at this stage."
Composed EDGE components flatten; only primitives reach the published tree. Documented Render, Publish, and Mount "Higher-level EDGE components you compose yourself flatten into these primitives; only primitive elements appear in the tree."
@press="refresh" is registered and replaced with a stable callback ID. Documented Render, Publish, and Mount Glossary "event handlers like @press="refresh" are registered and replaced with stable callback IDs"; Callback ID: "native events carry the ID back, and PHP resolves it to your method."
Publish: each element is written into shared memory as a node — a compact, fixed-layout binary record (type, layout, style, refs to props and handlers). Documented Render, Publish, and Mount Glossary "writes each element into shared memory as a node: a compact, fixed-layout binary record carrying the element's type, layout, style, and references to its props and handlers."
The same pipeline runs for first paint and for every update after. Documented Render, Publish, and Mount "The same pipeline runs for the first paint of a screen and for every update after it."
Unchanged subtrees become tiny reuse markers instead of being re-encoded. Documented Subtree Reuse "any subtree whose fingerprint matches the previous frame is written as a single tiny reuse marker instead of being re-encoded."
An identical frame isn't published at all. Documented Subtree Reuse "Identical frames are dropped on the spot — the native side is never even woken."
On mount, reuse markers are spliced from the previous tree without decoding. Documented Subtree Reuse "The native reader splices the corresponding subtree from the tree it already has, without decoding anything."
A dedicated native reader thread decodes frames and diffs against the previous tree. Documented Render, Publish, and Mount Threading Model "Each platform has a background thread that receives published frames, decodes them, and diffs them against the previous tree."
Publishing uses atomic version counters; it runs on the PHP thread, off the UI thread. Documented Threading Model "atomic version counters on the shared region"; "the PHP thread wakes, runs your handler, re-renders and publishes."
The loop: press event (carrying a callback ID) → PHP thread wakes → runs the handler → publishes → reader thread diffs → UI thread mounts. Documented Render, Publish, and Mount Threading Model Glossary "fires a press event carrying its callback ID into the event channel"; "the PHP thread wakes, runs your handler, re-renders and publishes → the reader thread diffs → the UI thread mounts the change."
Scroll, drag, and SharedValue-driven animation run on the UI thread frame-by-frame; PHP gets one discrete event, not a per-frame consult. Documented Threading Model About the New Architecture Glossary "Gestures and animations driven by SharedValues are evaluated directly on the UI thread at the display's frame rate… PHP receives one event when the gesture completes."
The value lives on the native side; PHP holds a handle to it. Documented Glossary About the New Architecture SharedValue: "A value that lives on the native side… PHP holds a handle."
One node abstraction, two native implementations; flexbox values drive a per-platform Layout. Documented Cross-Platform Implementation "each platform implements flexbox inside its own layout system — a pure-Swift Layout on iOS and a Compose Layout on Android."
It renders to SwiftUI on iOS and Jetpack Compose on Android. Documented Render, Publish, and Mount Cross-Platform Implementation SuperNative Introduction (docs) "a renderer that maps each node type to a SwiftUI view or a composable."
SuperNative is the default architecture in v4; the web view is opt-in per screen. Documented Blog: SuperNative "SuperNative is the default architecture"; web view is "explicitly opt-in" via <native:webview>.
The UI render/update path has no serialization step and no web-view bridge. Documented SuperNative Introduction (docs) "no network round-trip, no serialization overhead, and no waiting on a web view bridge."
super-native is a beta reference app, not for production. Documented Reference app (super-native) "Not for production. This is a reference app for exploring NativePHP's Element rendering system."
Reading the render pipeline as a chain of Gang-of-Four patterns — Interpreter, Composite, Command, Bridge, Proxy (plus Producer/Consumer and Reconciliation, which aren't GoF) — and arguing each is a "forced move" at a boundary. Interpretation The speaker's framing. Every pattern sits on a documented mechanism cited above; the names and the "forced move" thesis are the argument of the talk, not claims in the docs.
The node-vs-platform split, presented as the Gang-of-Four Bridge pattern. Interpretation Cross-Platform Implementation Mechanism is documented (see the per-platform Layout row); calling it "Bridge" is the speaker's reading.

The sources.

EXTERNAL LINKS — S1–S10
  1. S1 Render, Publish, and Mount nativephp.com
  2. S2 Subtree Reuse nativephp.com
  3. S3 Threading Model nativephp.com
  4. S4 Embedded PHP nativephp.com
  5. S5 Cross-Platform Implementation nativephp.com
  6. S6 Glossary nativephp.com
  7. S7 SuperNative Introduction (docs) nativephp.com
  8. S8 About the New Architecture nativephp.com
  9. S9 Blog: SuperNative nativephp.com
  10. S10 Reference app (super-native) github.com

Claims verified against the live NativePHP v4 docs on 2026-07-30. NativePHP v4 is a public beta; APIs may change and the reference app is explicitly not for production.