PHP finally has
real-time superpowers.
php-via turns your PHP into a reactive, multiplayer-ready server. No JavaScript. No build step. No API layer. Just PHP and OpenSwoole.
This is all the code.
A counter in ~15 lines of PHP. Switch the tab to see the only change needed to make it multiplayer — one line sets the scope. No JavaScript. No WebSockets. No API glue.
1<?php
2require __DIR__ . '/vendor/autoload.php';
3
4use Mbolli\PhpVia\Config;
5use Mbolli\PhpVia\Context;
6use Mbolli\PhpVia\Via;
7
8$app = new Via(new Config());
9
10$app->page('/', function (Context $c): void {
11 // TAB scope (default) — each visitor has their own counter
12 $count = $c->signal(0);
13
14 $increment = $c->action(
15 fn() => $count->setValue($count->int() + 1)
16 );
17
18 $c->view(fn() => <<<HTML
19 <p>Count: <strong data-text="$count">
20 {$count->int()}
21 </strong></p>
22 <button data-on:click="@post('{$increment->url()}')">
23 +1
24 </button>
25 HTML);
26});
27
28$app->start(); 1<?php
2require __DIR__ . '/vendor/autoload.php';
3
4use Mbolli\PhpVia\Config;
5use Mbolli\PhpVia\Context;
6 +use Mbolli\PhpVia\Scope;
7use Mbolli\PhpVia\Via;
8
9$app = new Via(new Config());
10
11$app->page('/', function (Context $c): void {
12 - // TAB scope (default) — each visitor has their own counter
13 + // GLOBAL scope — one shared counter for every visitor
14 + $c->scope(Scope::GLOBAL);
15 $count = $c->signal(0);
16
17 $increment = $c->action(
18 fn() => $count->setValue($count->int() + 1)
19 );
20
21 $c->view(fn() => <<<HTML
22 <p>Count: <strong data-text="$count">
23 {$count->int()}
24 </strong></p>
25 <button data-on:click="@post('{$increment->url()}')">
26 +1
27 </button>
28 HTML);
29});
30
31$app->start(); PHP deserves real-time.
The PHP ecosystem bent itself into a pretzel trying to add real-time to the stack. WebSockets bolted onto traditional PHP-FPM. Livewire polling every second. Inertia.js adding a React layer on top of Laravel. All of these add complexity, JavaScript, and build tooling to solve a problem that should be solved at the server.
php-via takes a different approach. Built on OpenSwoole's persistent event loop, every page maintains a live SSE connection. When your PHP changes state, the browser updates instantly — no polling, no WebSocket handshake ceremony, no client-side state management. The server is the source of truth, and the browser is a live view into it.
The scope system is the killer feature. Set a signal to
Scope::ROUTE and it automatically synchronizes across every browser
on that route. Change it to Scope::SESSION and it follows the user
across their tabs. Change it to Scope::GLOBAL and every user in
the entire application sees the update. One line of code changes who sees what.
We built this because PHP developers deserve the same multiplayer-by-default experience that Elixir's LiveView gives to Elixir developers. Without needing to learn Elixir.
Everything you need. Nothing you don't.
No JavaScript
Write server-side PHP. Datastar handles client-side reactivity through HTML attributes. Zero JavaScript to author.
No build step
php app.php and you're running. No bundler, no transpiler, no npm install before you can see a page.
Scoped state
TAB, ROUTE, SESSION, and GLOBAL scopes. One line changes whether state is private to a visitor or shared across everyone.
Multiplayer by default
Route-scoped signals broadcast to every user on the page. Build collaborative apps without pub/sub infrastructure.
Single SSE stream
One persistent connection per client carries all updates. Compresses exceptionally well with Brotli over a reverse proxy.
Components
Compose complex pages from isolated sub-contexts. Each component gets its own signals, actions, and rendering scope.
Vote. Watch it update everywhere.
Cast a vote. Every browser on this page sees the bars shift in real-time.
That's Scope::ROUTE broadcasting in action.
What's your favorite php-via scope?
Three primitives. That's the whole API.
Every php-via application is built from signals (reactive state), actions (event handlers), and views (rendering). The framework wires them together over SSE automatically.
Signal
Declare reactive state. The browser watches it and updates when it changes.
$c->signal(0, 'count')
Action
Handle browser events server-side. Modify signals in the handler.
$c->action(fn() => ...)
View
Render HTML with Twig or inline PHP. The framework pushes updates via SSE.
$c->view('page.twig')
Browser
Datastar applies the HTML patch. No page reload. No flash. Instant.
Honest comparison.
Every tool has trade-offs. Here's where php-via stands.
| Feature | php-via | Livewire | htmx + Alpine | Inertia.js | LiveView |
|---|---|---|---|---|---|
| Native real-time push | ✓ | ~Requires Laravel Reverb + Echo for server push; native Livewire uses HTTP round-trips. | ~SSE extension available but requires manual wiring. | ✗ | ✓ |
| Multiplayer / shared state | ✓ | ✗ | ✗ | ✗ | ✓ |
| Scoped state system | ✓ | ✗ | ✗ | ✗ | ~PubSub topics achieve similar results without a declarative scope system. |
| No JS to author | ✓ | ~Alpine.js is bundled; complex interactions may need JS hooks. | ✗ | ✗ | ~JS hooks needed for some client-side interactions. |
| No build step | ✓ | ✓ | ✓ | ✗ | ✗ |
| File uploads | ✗ | ✓ | ✓ | ✓ | ✓ |
| Form validation | ✗ | ✓ | ~HTML5 constraint API only; no built-in server-side validation. | ✓ | ✓ |
| Auth / middleware | ✗ coming soon | ✓ | ✓ | ✓ | ✓ |
| Ecosystem & community | Early | Large | Medium | Large | Large |
| Multi-server scaling | ✗ | ✓ | ✓ | ✓ | ✓ |
| Framework required | No | Laravel | No | AnyPrimarily targets Laravel but also supports Rails, Django, and Phoenix. | Phoenix |
| Language | PHP | PHP | PHP + JS | PHP + JS | Elixir |
| Runtime | OpenSwoole | PHP-FPM | PHP-FPM | PHP-FPM | BEAM VM |
Try it in under 5 minutes.
composer require mbolli/php-via