Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,72 @@ title: About the New Architecture
order: 10
---

NativePHP for Mobile runs your Laravel app on the device itself — no server, no network round-trip. Traditionally,
your app's UI rendered as HTML inside a web view. That model is productive and familiar, and it's
NativePHP for Mobile runs your Laravel app on the device itself. There is no server and no network round-trip.
Until now, your app's UI rendered as HTML inside a web view. That model is productive and familiar, and it's
[still fully supported](../architecture/super-native#is-the-web-view-still-an-option). But it puts a browser between
your app and the platform, and some things can only feel truly native when they *are* native.
your app and the platform, and some things only feel native when they *are* native.

The new architecture — [SuperNative](../architecture/super-native) — removes that layer entirely. Your screens are
real SwiftUI and Jetpack Compose views, created and updated directly by your PHP code. Here's why we built it, and
what it changes.
The new architecture, [SuperNative](../architecture/super-native), removes that layer. Your screens are real SwiftUI
and Jetpack Compose views, created and updated directly by your PHP code. Here's why we built it and what it
changes.

## Why a new architecture?

### Truly native rendering

A web view is a remarkable piece of engineering, but users can tell. Scroll physics, text rendering, transitions,
context menus, dark mode, dynamic type, screen readers — every one of these is approximated in a browser and
effortless in the platform's own UI framework.
context menus, dark mode, dynamic type, screen readers. Every one of these is approximated in a browser and comes
for free in the platform's own UI framework.

With the new architecture, an EDGE component like `<native:button>` isn't a styled `<div>` — it's the same button
every other native app on that device uses. Accessibility, theming and platform conventions come along for free,
With the new architecture, an EDGE component like `<native:button>` is the same button every other native app on
that device uses, rather than a styled `<div>`. Accessibility, theming and platform conventions come along with it,
because there's nothing between your UI and the operating system.

### Shared memory, not serialization
### Shared memory instead of serialization

Frameworks that drive native UI from another language usually pay a toll at the border: state gets serialized to
JSON, shipped across a bridge, and parsed on the other side — for every update.
Frameworks that drive native UI from another language usually pay a toll at the border. State gets serialized to
JSON, shipped across a bridge, and parsed on the other side, on every update.

NativePHP doesn't have that border. Your PHP runtime is [embedded inside the app process](embedded-php), and the
rendering layer communicates through **shared memory**. When your component re-renders, PHP writes a compact binary
description of the screen directly into a buffer the native side reads from — no JSON, no sockets, no copies across
a bridge. The native layer walks your PHP data structures directly, in the same process, in native code.
description of the screen directly into a buffer the native side reads from. There is no JSON and no bridge to
cross. The native layer walks your PHP data structures directly, in the same process, in native code.

The result: a state change in PHP reaches the screen in well under a frame.
A state change in PHP reaches the screen in well under a frame.

### Native-speed interaction

Some things should never wait for application code — a drag should track your finger, an animation should never
drop a frame. The new architecture has a dedicated lane for these:
SharedValues live on the native side and are updated on the UI thread by gestures and animations at the display's
full frame rate. PHP holds a handle and hears about the outcome; the
per-frame work never crosses into PHP at all.
Some things should never wait for application code. A drag should track your finger, and an animation should never
drop a frame. The new architecture has a dedicated lane for these. SharedValues live on the native side and are
updated on the UI thread by gestures and animations at the display's full frame rate. PHP holds a handle and hears
about the outcome. The per-frame work never crosses into PHP at all.

You get Reanimated-style, gesture-driven motion — written in Blade.
You get Reanimated-style, gesture-driven motion, written in Blade.

## What can you expect?

Practically, the same development experience you already have, with a different result on screen:
Practically, the same development experience you already have, with a different result on screen.

- **Same Laravel app.** Routes, controllers, Eloquent, validation, queues — nothing about your backend changes.
Screens are registered with `Route::native()` and driven by PHP component classes, Livewire-style.
- **Same Blade.** You compose screens from [EDGE components](../edge-components/introduction) — declarative Blade
tags with Tailwind-style utility classes — and the framework turns them into SwiftUI and Compose.
- **Native output.** Navigation stacks, tab bars, sheets, lists and gestures are the platform's own, in light and
- Your Laravel app stays the same. Routes, controllers, Eloquent, validation and queues are untouched. Screens are
registered with `Route::native()` and driven by PHP component classes, Livewire-style.
- Your Blade stays the same. You compose screens from [EDGE components](../edge-components/introduction),
declarative Blade tags with Tailwind-style utility classes, and the framework turns them into SwiftUI and Compose.
- The output is native. Navigation stacks, tab bars, sheets, lists and gestures are the platform's own, in light and
dark mode, with the platform's accessibility support built in.
- **Adopt at your pace.** The web view is still available as a component, so you can go all-native, all-web, or
- You adopt at your own pace. The web view is still available as a component, so you can go all-native, all-web, or
migrate screen by screen.

<aside>

The new architecture makes a different *class* of app possible — it doesn't automatically make an existing web-view
The new architecture makes a different *class* of app possible. It doesn't automatically make an existing web-view
app faster. If your app is happy in the web view, it will keep working exactly as before.

</aside>

## Should you use it today?

SuperNative is **the default**: new apps render native screens from the very first route. It's in beta, so
expect rapid iteration — and if you'd rather wait, [opting out](../architecture/super-native#is-the-web-view-still-an-option)
is one route and one component.
SuperNative is **the default**. New apps render native screens from the very first route. It's in beta, so expect
rapid iteration. If you'd rather wait,
[opting out](../architecture/super-native#is-the-web-view-still-an-option) is one route and one component.

Ready to go deeper? Start with [The Renderer](renderer).
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,54 @@ title: Cross-Platform Implementation
order: 40
---

NativePHP renders with each platform's own UI framework — SwiftUI on iOS, Jetpack Compose on Android — yet one
Blade template produces the same screen on both. That consistency isn't achieved by testing hard; it's achieved by
sharing the core.
NativePHP renders with each platform's own UI framework, SwiftUI on iOS and Jetpack Compose on Android, yet one
Blade template produces the same screen on both. One shared core is what makes that possible.

## One core, two thin shells

Everything that defines *what* gets rendered lives in a single shared implementation, written in C and
[compiled directly into the PHP runtime](embedded-php) that ships on both platforms:

- walking your PHP element trees and encoding them into [frames](glossary#frame),
- the binary [node](glossary#node) format itself — every field, in the same order, with the same meaning,
- the binary [node](glossary#node) format itself, every field in the same order with the same meaning,
- the skip-and-reuse logic described in [Subtree Reuse](subtree-reuse),
- and the [event channel](glossary#wire-events) that carries interactions back to PHP.

What remains per platform is deliberately thin: a reader that decodes frames and diffs trees, and a renderer that
maps each node type to a SwiftUI view or a composable. Even those two layers are written as mirrors of each other
— the same diffing rules, the same frame-coalescing behavior, the same enums for flexbox values, so
maps each node type to a SwiftUI view or a composable. Even those two layers are written as mirrors of each other,
with the same diffing rules, the same frame-coalescing behavior and the same enums for flexbox values, so
`justify-content: center` is the identical byte on the wire and the identical concept on both screens.

The practical consequence: when we fix a bug or land a performance improvement in the pipeline, both platforms get
it simultaneously, because there's only one pipeline.
In practice, when we fix a bug or land a performance improvement in the pipeline, both platforms get it at the same
time, because there's only one pipeline.

## Native layout on both platforms

Layout follows the same philosophy. Each node carries flexbox-style layout values, and each platform implements
flexbox *inside its own layout system* — a pure-Swift `Layout` on iOS and a Compose `Layout` on Android, built to
the same semantics. There's no shared C++ layout engine sitting apart from the UI framework: your components
measure and place like any other SwiftUI or Compose view, which is exactly why safe areas, dynamic type and
platform navigation transitions work naturally with them.
flexbox *inside its own layout system*, a pure-Swift `Layout` on iOS and a Compose `Layout` on Android, built to
the same semantics. There's no shared C++ layout engine sitting apart from the UI framework. Your components
measure and place like any other SwiftUI or Compose view, which is why safe areas, dynamic type and platform
navigation transitions work naturally with them.

iOS is the reference implementation for visual behavior; Android is held to it. Where the platforms disagree by
default (text padding, stretch behavior, pixel-versus-point units), the Android renderer adapts so the same
classes produce the same result.

## Two seams, one boundary

Rendering isn't the only traffic between PHP and native. Device features — camera, biometrics, geolocation,
secure storage and everything the [plugin system](../plugins/introduction) exposes — travel over a separate,
Rendering isn't the only traffic between PHP and native. Device features (camera, biometrics, geolocation,
secure storage and everything the [plugin system](../plugins/introduction) exposes) travel over a separate,
simpler seam: [bridge functions](../plugins/bridge-functions), a registry of named native functions PHP can call
with JSON in and JSON out.

The two seams are independent by design. The rendering path is a hot loop measured in microseconds, so it gets the
binary shared-memory treatment; device APIs are occasional calls where clarity and extensibility matter more, so
binary shared-memory treatment. Device APIs are occasional calls where clarity and extensibility matter more, so
they get a friendly, pluggable interface. Plugin authors only ever touch the second one.

## Failing loud, not weird

A shared binary format is only safe if both sides agree on it, so the format carries an explicit **version
number**. At startup, each native reader checks the version reported by the Element Runtime against the one it was
compiled for; on mismatch it refuses to render rather than misinterpret bytes. Because the PHP runtime and the
native shells [ship together in lockstep](embedded-php), the check should never fire in a real app — it exists so
native shells [ship together in lockstep](embedded-php), the check should never fire in a real app. It exists so
that if the impossible happens, you get one clear error instead of a subtly broken UI.
28 changes: 14 additions & 14 deletions resources/views/docs/mobile/4/architecture/embedded-php.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,49 @@ title: Embedded PHP
order: 70
---

There's no server in a NativePHP app — and no separate PHP process either. PHP ships *inside* your app as a
There's no server in a NativePHP app, and no separate PHP process either. PHP ships *inside* your app as a
library, compiled specifically for mobile devices and linked into the native application itself. This page
explains what that build is, why we produce it ourselves, and how it reaches your project.

## PHP as a library

Standard PHP is built to sit behind a web server. Mobile PHP is built with the **embed SAPI**: instead of a
`php` binary, the build produces a C library (`libphp`) that the Swift and Kotlin shells link against and call
directly. When your app boots, it initializes the PHP engine in-process, loads your Laravel app from the bundled
Standard PHP is built to sit behind a web server. Mobile PHP is built with the **embed SAPI**. Instead of a `php`
binary, the build produces a C library (`libphp`) that the Swift and Kotlin shells link against and call directly.
When your app boots, it initializes the PHP engine in-process, loads your Laravel app from the bundled
[app package](../getting-started/introduction), and keeps it resident as the
[persistent runtime](threading-model#lifecycle-booted-once-kept-warm).

Running in-process is what makes the rest of the architecture possible: shared memory only works when both sides
share a process. It's also simply fast — there's no FastCGI, no sockets, no per-request process to spawn.
share a process. It's also fast, since there's no FastCGI layer and no per-request process to spawn.

NativePHP for Mobile currently bundles **PHP 8.4**, cross-compiled for iOS (device and simulator) and Android
(ARM64), thread-safe, and tuned for embedding.

## Built in lockstep with the framework

The renderer's [wire format](glossary#frame) has two sides: the [Element Runtime](glossary#element-runtime) that
writes it, and the native readers that decode it. The Element Runtime is a PHP **extension** — native code
writes it, and the native readers that decode it. The Element Runtime is a PHP **extension**, native code
compiled into `libphp` itself, alongside the engine.

That's why we build PHP ourselves, and why every release of `nativephp/mobile` pins exact binary builds: the PHP
That's why we build PHP ourselves, and why every release of `nativephp/mobile` pins exact binary builds. The PHP
engine, the NativePHP extension inside it, and the Swift/Kotlin readers around it are produced and shipped
**together**, from matching sources. Both sides of every boundary are always the same version — there is no
**together**, from matching sources. Both sides of every boundary are always the same version, so there is no
"which PHP works with which framework release" matrix to manage. The wire format's
[version handshake](cross-platform-implementation#failing-loud-not-weird) exists as a backstop, but lockstep
shipping is what makes it a formality.

The same extension also provides the [bridge functions](../plugins/bridge-functions) seam that device APIs and
plugins are built on — one native extension, both boundaries.
plugins are built on. One native extension, both boundaries.

## What's inside

The builds include the extensions a Laravel app expects, statically compiled along with their dependencies:
bcmath, ctype, curl, dom, fileinfo, filter, intl, mbstring, openssl, pdo_sqlite, phar, session, simplexml,
sockets, sodium, sqlite3, tokenizer, xml, xmlreader, xmlwriter, zip and zlib — plus the `nativephp` extension
sockets, sodium, sqlite3, tokenizer, xml, xmlreader, xmlwriter, zip and zlib, plus the `nativephp` extension
described above. SQLite is the bundled database, a CA certificate bundle is included so HTTPS works out of the
box, and ICU is included so localization and `intl`-powered formatting behave properly on device.

Everything is compiled from source for each platform — including OpenSSL, libxml2, SQLite and friends — so the
Everything is compiled from source for each platform, including OpenSSL, libxml2, SQLite and friends, so the
builds have no dependencies on whatever libraries happen to exist on a given device.

## How it reaches your app
Expand All @@ -57,12 +57,12 @@ php artisan native:install
```

the framework downloads the prebuilt PHP bundles matching your `nativephp/mobile` version and places them into the
platform projects — static libraries for iOS, shared libraries for Android. `native:run` then compiles your app
with PHP inside it, exactly like any other native dependency.
platform projects, as static libraries for iOS and shared libraries for Android. `native:run` then compiles your
app with PHP inside it, exactly like any other native dependency.

<aside>

These builds are compiled specifically for mobile targets and can't be used as a general-purpose PHP — your
These builds are compiled specifically for mobile targets and can't be used as a general-purpose PHP. Your
development machine keeps using its own PHP for `artisan`, tests and tooling. Just make sure your app code is
happy on the bundled PHP version.

Expand Down
Loading