PHP coerces numeric-string array keys ("1") to int keys, so ids()/each()/clear()
could hand ints to remove(string) and TypeError. Cast to string in those paths.
Surfaced by the laravel-ws integration (connection ids are sequential numbers).
Regression test added. Suite: 20 tests.
rel learn-atc #1055 #1057
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| composer.json | ||
| phpunit.xml.dist | ||
| pint.json | ||
| test.sh | ||
README.md
ReactPHP Kernel
A tiny, protocol-agnostic ReactPHP backbone — one long-lived process, one event loop, one IPC primitive, and one graceful-shutdown story that many protocol servers attach to.
Features
- 🧩 One loop, many protocols — WebSockets, WebRTC signaling, SIP and more share a single
Kernelinstead of each spinning up its own runtime - 🔌 Tiny
Servercontract — implementboot(LoopInterface)+shutdown()and you're on the shared loop - 🧵 Socket-pair IPC — event-driven parent↔child messaging with zero polling
- 🛎️ Graceful shutdown — SIGINT/SIGTERM tear every server down cleanly before the loop stops
- 🧹 Child reaping built in — no zombie processes from forked workers (SIGCHLD + periodic backstop)
- 🔐 Plain or TLS listeners — one factory, no framework coupling
- 📝 PSR-3 logging — pass any logger; the kernel emits lifecycle lines and hands it to your servers
- 📇 Connection registry — a shared presence/broadcast map (channel members, WebRTC peers) any protocol can reuse
- 🪶 Dependency-light —
psr/log+react/event-loop,react/socket,react/stream
Installation
composer require blax-software/reactphp-kernel
Requires PHP 8.1+, plus ext-pcntl (signals + child reaping) and ext-sockets (IPC) for the full feature set.
Quick Start
Implement a Server, register it, run():
use Blax\ReactPhpKernel\Kernel;
use Blax\ReactPhpKernel\Contracts\Server;
use Blax\ReactPhpKernel\Server\SocketServerFactory;
use React\EventLoop\LoopInterface;
use React\Socket\ConnectionInterface;
final class EchoServer implements Server
{
private $socket = null;
public function name(): string
{
return 'echo';
}
public function boot(LoopInterface $loop): void
{
$this->socket = SocketServerFactory::create('0.0.0.0:9001', $loop);
$this->socket->on('connection', function (ConnectionInterface $conn) {
$conn->on('data', fn ($data) => $conn->write($data)); // echo it back
});
}
public function shutdown(): void
{
$this->socket?->close();
}
}
(new Kernel())
->reapChildren() // auto-reap forked children
->register(new EchoServer())
->onBoot(fn (Kernel $k) => fwrite(STDERR, "ready\n"))
->run(); // boots servers, installs signals, runs the loop (blocks)
Ctrl-C (SIGINT) or SIGTERM triggers a graceful stop(): every registered server's shutdown() runs, then the loop stops.
What's in the box
| Class | Responsibility |
|---|---|
Kernel |
Owns the loop; register() servers; installs signals; run() / graceful stop(). |
Contracts\Server |
An attachable protocol server: boot(LoopInterface) (non-blocking) + shutdown(). |
Server\SocketServerFactory |
Plain-TCP or TLS listening socket. |
Ipc\SocketPairIpc |
Event-driven parent/child IPC over a Unix socket pair. |
Process\SignalHandler |
SIGINT/SIGTERM → graceful shutdown. |
Process\ChildReaper |
Reaps exited forked children (SIGCHLD + periodic backstop). |
Support\ConnectionRegistry |
Shared id→connection registry for presence/broadcast. |
Why
It's the shared foundation extracted from blax-software/laravel-websockets so that WebSockets and blax-software/laravel-webrtc (WebRTC signaling + a Rust/str0m media core) don't each re-implement the loop, IPC, signals and supervision. A new protocol gets the plumbing for free.
reactphp-kernel this package — the backbone
├─ laravel-websockets WS on the kernel
└─ laravel-webrtc WebRTC signaling on the kernel
└─ Rust/str0m core via ext-php-rs
Testing
composer install
composer test
The suite runs against a real ReactPHP loop, with no external services.
License
MIT. See LICENSE.