https://github.com/mbolli/php-via
Real-time engine for building reactive web applications in PHP with Swoole.
https://github.com/mbolli/php-via
datastar php-framework reactive swoole
Last synced: 3 months ago
JSON representation
Real-time engine for building reactive web applications in PHP with Swoole.
- Host: GitHub
- URL: https://github.com/mbolli/php-via
- Owner: mbolli
- License: mit
- Created: 2025-11-26T14:56:39.000Z (7 months ago)
- Default Branch: master
- Last Pushed: 2025-12-21T12:46:40.000Z (7 months ago)
- Last Synced: 2025-12-23T03:19:18.215Z (7 months ago)
- Topics: datastar, php-framework, reactive, swoole
- Language: PHP
- Homepage: https://via.zweiundeins.gmbh
- Size: 1.35 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# php-via
[](https://packagist.org/packages/mbolli/php-via)
[](https://packagist.org/packages/mbolli/php-via)
[](https://packagist.org/packages/mbolli/php-via)
[](https://packagist.org/packages/mbolli/php-via)
[](https://github.com/mbolli/php-via/actions)
[](https://phpstan.org/)
[](https://via.zweiundeins.gmbh)
Real-time reactive web framework for PHP. Server-side reactive UIs with zero JavaScript, using [OpenSwoole](https://openswoole.com/) for async PHP, [Datastar](https://data-star.dev) (RC.8) for SSE + DOM morphing, and [Twig](https://twig.symfony.com/) for templating.
**[Documentation & Live Examples](https://via.zweiundeins.gmbh)**
## Why php-via?
- **No JavaScript to write** — Datastar handles client-side reactivity, SSE, and DOM morphing
- **Twig templates** — familiar, powerful server-side templating
- **No build step** — no transpilation, no bundling, no node_modules
- **Real-time by default** — every page gets a live SSE connection
- **Scoped state** — TAB, ROUTE, SESSION, GLOBAL, and custom scopes control who shares what
- **Single SSE stream** — extremely efficient with Brotli compression
## Requirements
- PHP 8.4+
- OpenSwoole extension
- Composer
## Installation
```
composer require mbolli/php-via
```
## Quick Start
```php
withTemplateDir(__DIR__ . '/templates');
$app = new Via($config);
$app->page('/', function (Context $c): void {
$count = $c->signal(0, 'count');
$step = $c->signal(1, 'step');
$increment = $c->action(function () use ($count, $step, $c): void {
$count->setValue($count->int() + $step->int());
$c->syncSignals();
}, 'increment');
$c->view('counter.html.twig', [
'count' => $count,
'step' => $step,
'increment' => $increment,
]);
});
$app->start();
```
**counter.html.twig:**
```twig
Count: {{ count.int }}
Step:
Increment
```
```
php app.php
# → http://localhost:3000
```
## Core Concepts
Full documentation at **[via.zweiundeins.gmbh/docs](https://via.zweiundeins.gmbh/docs)**
### Signals — reactive state that syncs between server and client
```php
$name = $c->signal('Alice', 'name');
$name->string(); // read
$name->setValue('Bob'); // write → auto-pushes to browser
```
```twig
{{ name.string }}
```
### Actions — server-side functions triggered by client events
```php
$save = $c->action(function () use ($c): void {
$c->sync();
}, 'save');
```
```twig
Save
```
### Scopes — control who shares state and receives broadcasts
| Scope | Sharing | Use Case |
|-------|---------|----------|
| `Scope::TAB` | Isolated per tab (default) | Personal forms, settings |
| `Scope::ROUTE` | All users on same route | Shared boards, multiplayer |
| `Scope::SESSION` | All tabs in same session | Cross-tab state |
| `Scope::GLOBAL` | All users everywhere | Notifications, announcements |
| Custom (`"room:lobby"`) | All contexts in that scope | Chat rooms, game lobbies |
### Views — Twig template files or inline strings
```php
$c->view('dashboard.html.twig', ['user' => $user]);
```
### Path Parameters — auto-injected by name
```php
$app->page('/blog/{year}/{slug}', function (Context $c, string $year, string $slug): void {
// ...
});
```
### Components — reusable sub-contexts with isolated state
```php
$a = $c->component($counterWidget, 'a');
$b = $c->component($counterWidget, 'b');
```
### Lifecycle Hooks
```php
$c->onDisconnect(fn() => /* cleanup */);
$c->setInterval(fn() => $c->sync(), 2000); // auto-cleaned on disconnect
$app->onClientConnect(fn(string $id) => /* ... */);
```
### Broadcasting — push updates to other connected clients
```php
$c->broadcast(); // same scope
$app->broadcast(Scope::GLOBAL); // all contexts
$app->broadcast('room:lobby'); // custom scope
```
## How it Works
```
1. Browser requests page → Server renders HTML, opens SSE stream
2. User clicks button → Datastar POSTs signal values + action ID
3. Server executes action → Modifies signals / state
4. Server pushes patches → HTML fragments + signal updates via SSE
5. Datastar morphs DOM → UI updates without page reload
```
## Development
```
git clone https://github.com/mbolli/php-via.git
cd php-via && composer install
cd website && php app.php # run website + examples on :3000
vendor/bin/pest # 101 tests, 258 assertions
composer phpstan # PHPStan level 6
composer cs-fix # code style
```
## Deployment
Single OpenSwoole process behind a reverse proxy. See [deploy/](deploy/) for systemd + Caddy configs.
```
Browser → Caddy (TLS + Brotli) → OpenSwoole :3000
```
## Roadmap
- [ ] Route groups (`$app->group('/prefix', fn)`)
- [ ] `initAtBoot()` — explicit hook for boot-time shared state initialisation
- [ ] Global intervals (`$app->setInterval()` — one shared timer per server process)
## Credits
- [Datastar](https://data-star.dev/) — SSE + DOM morphing
- [OpenSwoole](https://openswoole.com/) — Async PHP
- [Twig](https://twig.symfony.com/) — Templating
- [go-via/via](https://github.com/go-via/via) — Original Go inspiration
## License
MIT
