WebSockets
Info: Modular Architecture: Core implementation uses PHP 8.5 Fibers with zero dependencies. For high-scale production, drop in the
larafony/websocket-reactbridge package.
Overview
Larafony's WebSocket system provides complete real-time communication capabilities:
-
RFC 6455 Compliant - Full WebSocket protocol with proper framing and handshakes
-
Zero Dependencies - Core uses only PHP 8.5 Fibers and ext-sockets
-
Swappable Engines - FiberEngine (core) or ReactEngine (bridge)
-
Event-Based - Simple
on('event', callback)API -
Broadcast Support - Send to all or filtered connections
-
Service Provider - Extend to register custom handlers
Quick Start
1. Basic Server
use Larafony\Framework\WebSockets\Engine\FiberEngine;
use Larafony\Framework\WebSockets\Server;
$server = new Server(new FiberEngine(), '0.0.0.0', 8080);
$server->on('open', function ($data, $connection) {
echo "Connected: {$connection->getId()}\n";
});
$server->on('message', function ($payload, $connection) {
$connection->send("Echo: {$payload}");
});
$server->on('close', function ($data, $connection) {
echo "Disconnected: {$connection->getId()}\n";
});
$server->run();
2. Using Console Command
# Start with default config
php bin/larafony websocket:start
# Custom host and port
php bin/larafony websocket:start --host=127.0.0.1 --port=9000
3. Configuration
// config/websocket.php
return [
'host' => env('WEBSOCKET_HOST', '0.0.0.0'),
'port' => (int) env('WEBSOCKET_PORT', 8080),
];
Architecture
Success: Clean Separation: Protocol logic (Frame, Encoder, Decoder) is shared between engines. Only the I/O layer differs.
┌─────────────────────────────────────────────────────┐
│ SHARED PROTOCOL LAYER │
├─────────────────────────────────────────────────────┤
│ Frame, Encoder, Decoder, Opcode, Handshake │
│ Server, EventDispatcher, Connection logic │
├─────────────────────────────────────────────────────┤
│ ENGINE ABSTRACTION (EngineContract) │
├──────────────────────┬──────────────────────────────┤
│ FiberEngine │ ReactEngine │
│ (Core - no deps) │ (Bridge - react/*) │
└──────────────────────┴──────────────────────────────┘
Custom Event Handling
The server automatically parses JSON messages with event field:
// Client sends:
ws.send(JSON.stringify({
event: 'chat_message',
data: { message: 'Hello!' }
}));
// Server handles:
$server->on('chat_message', function ($data, $connection) {
$message = $data['message'];
// Process chat message...
});
Broadcasting
// Broadcast to all connections
$server->broadcast('Hello everyone!');
// Broadcast with filter (exclude sender)
$server->broadcast(
json_encode(['type' => 'notification', 'text' => 'New message']),
fn($conn) => $conn->getId() !== $currentConnection->getId()
);
Service Provider Pattern
Extend WebSocketServiceProvider to register your handlers:
namespace App\Providers;
use Larafony\Framework\WebSockets\Contracts\ServerContract;
use Larafony\Framework\WebSockets\ServiceProviders\WebSocketServiceProvider;
class ChatWebSocketProvider extends WebSocketServiceProvider
{
protected function registerDefaultHandlers(ServerContract $server): void
{
$server->on('chat_message', function ($data, $connection) {
// Handle chat messages
});
$server->on('user_typing', function ($data, $connection) use ($server) {
// Broadcast typing indicator
$server->broadcast(
json_encode(['event' => 'typing', 'user' => $data['userId']]),
fn($c) => $c->getId() !== $connection->getId()
);
});
}
}
Bridge Package: ReactPHP
For production environments requiring high concurrency:
composer require larafony/websocket-react
// Use ReactWebSocketServiceProvider instead of WebSocketServiceProvider
use Larafony\WebSocket\ReactWebSocketServiceProvider;
class MyReactProvider extends ReactWebSocketServiceProvider
{
protected function registerDefaultHandlers(ServerContract $server): void
{
// Same API as FiberEngine!
$server->on('message', fn($data, $conn) => ...);
}
}
Warning: Performance: FiberEngine handles ~1000 concurrent connections. ReactEngine scales to 10,000+ with libuv/libev event loops.
Protocol Components
Frame
use Larafony\Framework\WebSockets\Protocol\Frame;
// Create frames with factory methods
$textFrame = Frame::text('Hello');
$binaryFrame = Frame::binary($data);
$pingFrame = Frame::ping();
$closeFrame = Frame::close(1000, 'Normal closure');
// Send directly
$connection->send($textFrame);
$connection->send('Or just a string');
Opcode Enum
use Larafony\Framework\WebSockets\Protocol\Opcode;
Opcode::TEXT; // 1
Opcode::BINARY; // 2
Opcode::CLOSE; // 8
Opcode::PING; // 9
Opcode::PONG; // 10
$opcode->isControl(); // true for CLOSE, PING, PONG
Client-Side (Vue.js Example)
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ai_response') {
displayMessage(data.data);
}
};
const sendMessage = (message) => {
ws.send(JSON.stringify({
event: 'chat_message',
data: { message }
}));
};
Practical Example: AI Chat in demo-app 🤖
Complete demonstration of WebSocket integration with AI - from Vue frontend through WebSocket to backend calling OpenAI API.
Controller (Inertia)
demo-app/src/Controllers/ChatAIController.php
class ChatAIController extends Controller
{
public function index(ConfigContract $config): \Inertia\Response
{
return inertia('Chat/Index', [
'wsHost' => $config->get('websocket.host', 'localhost'),
'wsPort' => $config->get('websocket.port', 8080),
]);
}
}
Message Listener with OpenAI
demo-app/src/Listeners/ChatMessageListener.php
class ChatMessageListener
{
public function __construct(
private readonly ConfigContract $config,
) {}
public function __invoke(array $data, ConnectionContract $connection): void
{
$message = $data['message'] ?? '';
if (empty($message)) return;
$response = $this->callOpenAI($message);
$connection->send(Frame::text(json_encode([
'event' => 'ai_response',
'data' => ['message' => $response, 'timestamp' => time()],
])));
}
private function callOpenAI(string $message): string
{
$client = new CurlHttpClient();
$apiKey = $this->config->get('openai.api_key');
$request = new Request(
'POST',
new Uri('https://api.openai.com/v1/chat/completions'),
['Content-Type' => 'application/json', 'Authorization' => "Bearer {$apiKey}"],
json_encode([
'model' => $this->config->get('openai.model', 'gpt-4'),
'messages' => [['role' => 'user', 'content' => $message]],
])
);
$response = $client->sendRequest($request);
$body = json_decode((string) $response->getBody(), true);
return $body['choices'][0]['message']['content'] ?? 'Error';
}
}
ServiceProvider
demo-app/src/Providers/ChatWebSocketProvider.php
class ChatWebSocketProvider extends WebSocketServiceProvider
{
protected function registerDefaultHandlers(ServerContract $server): void
{
$listener = $this->container->get(ChatMessageListener::class);
$server->on('chat_message', $listener);
$server->on('open', fn($data, $conn) =>
$conn->send(json_encode(['event' => 'welcome', 'data' => 'Connected']))
);
}
}
Vue Component
demo-app/resources/js/Pages/Chat/Index.vue
const ws = new WebSocket(`ws://${props.wsHost}:${props.wsPort}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ai_response') {
messages.value.push({
type: 'ai',
text: data.data.message,
timestamp: data.data.timestamp,
});
}
};
function sendMessage() {
ws.send(JSON.stringify({
event: 'chat_message',
data: { message: newMessage.value },
}));
}
Data Flow
-
User types message in Vue component
-
Vue sends JSON via WebSocket:
{"event": "chat_message", "data": {"message": "..."}} -
Server dispatches
chat_messageevent to ChatMessageListener -
Listener calls OpenAI API via PSR-18 CurlHttpClient
-
AI response sent back via WebSocket
-
Vue receives and displays response in real-time
The entire flow works without page reload, with instant response thanks to persistent WebSocket connection.
Framework Comparison 🔥
How does Larafony's WebSocket implementation compare to other PHP frameworks?
vs Laravel Reverb
Laravel introduced Reverb in 2024 as its first-party WebSocket solution:
| Aspect | Laravel Reverb | Larafony | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Dependencies | Ratchet, Redis (for scaling) | **Zero** (core), ReactPHP (optional) | |||||||||||||||||||||||||||||||||
| Protocol | Pusher protocol | **Native RFC 6455** | |||||||||||||||||||||||||||||||||
| Architecture | Separate Reverb server process | **Integrated into framework** | |||||||||||||||||||||||||||||||||
| Scaling | Requires Redis pub/sub | **Built-in broadcast** | |||||||||||||||||||||||||||||||||
| Learning Curve | Pusher concepts, channels, events | **Simple `on('event', callback)`** | |||||||||||||||||||||||||||||||||
| External Services | Often paired with Pusher/Soketi | **Fully self-contained**
vs SymfonySymfony does not include a built-in WebSocket solution:
|