cboden/ratchet
Ratchet is a PHP library for building asynchronous WebSocket servers. Compose apps from simple interfaces, reuse components, and deploy behind proxies or on ports 80/443. Includes docs and examples for chat-style real-time messaging.
## Getting Started
### **Minimal Setup**
1. **Installation**
```bash
composer require cboden/ratchet
Ratchet v0.4.4 now requires PHP 8.0+ and the ext-pcntl and ext-posix extensions. Verify compatibility with:
composer validate --strict
Basic WebSocket Server
Create a minimal server in public/ws-server.php (updated for ReactPHP context):
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\WebSocket\MyWebSocket;
use React\EventLoop\Factory;
require __DIR__.'/../vendor/autoload.php';
$loop = Factory::create();
$server = IoServer::factory(
new HttpServer(
new WsServer(
new MyWebSocket($loop) // Pass ReactPHP loop for context
)
),
8080,
'0.0.0.0'
);
$server->run();
$loop->run();
Define a WebSocket Class Update the constructor to accept ReactPHP context:
<?php
namespace MyApp\WebSocket;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use React\EventLoop\LoopInterface;
class MyWebSocket implements MessageComponentInterface {
protected $loop;
public function __construct(LoopInterface $loop) {
$this->loop = $loop;
}
public function onOpen(ConnectionInterface $conn) {
echo "New connection! ({$conn->resourceId})\n";
}
// ... (rest of methods remain unchanged)
}
Run the Server
php public/ws-server.php
Test with a WebSocket client (e.g., wscat):
wscat -c ws://localhost:8080
Extend MyWebSocket to broadcast messages (updated for ReactPHP context):
public function onMessage(ConnectionInterface $from, $msg) {
$this->loop->futureTick(function() use ($from, $msg) {
foreach ($this->clients as $client) {
if ($client !== $from) {
$client->send("{$from->resourceId}: {$msg}");
}
}
});
}
ReactPHP Integration: Use the loop context for async operations:
public function onMessage($msg) {
$this->loop->addTimer(0.1, function() use ($msg) {
// Non-blocking DB call via Laravel Queues
dispatch(new ProcessMessage($msg))->onQueue('websockets');
});
}
Dependency Injection with ReactPHP: Bind the loop in Laravel’s service provider:
public function register() {
$this->app->singleton(LoopInterface::class, function() {
return Factory::create();
});
$this->app->bind(MyWebSocket::class, function($app) {
return new MyWebSocket($app->make(LoopInterface::class));
});
}
Route WebSocket to Laravel: Updated Nginx config for ReactPHP compatibility:
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Authenticate with ReactPHP: Use async token validation:
public function onOpen(ConnectionInterface $conn) {
$token = $conn->httpRequest->getHeader('Authorization')[0] ?? null;
$this->loop->futureTick(function() use ($conn, $token) {
if (!auth()->validateToken($token)) {
$conn->close();
return;
}
$this->clients->attach($conn);
});
}
public function onMessage($msg) {
$future = new React\Promise\Deferred();
event(new MessageReceived($msg, $future));
$future->promise()->then(
function($result) use ($msg) {
foreach ($this->clients as $client) {
$client->send($result);
}
}
);
}
use Ratchet\Balancer\RedisBalancer;
$balancer = new RedisBalancer('127.0.0.1', 6379, $loop);
$server = IoServer::factory(
new BalancingServer(
new WsServer(new MyWebSocket($loop)),
$balancer
),
8080
);
Unit Test Handlers: Mock ReactPHP loop:
$loop = Mockery::mock(LoopInterface::class);
$handler = new MyWebSocket($loop);
$conn = Mockery::mock(ConnectionInterface::class);
$handler->onOpen($conn);
Integration Test: Use ReactPHP test utilities:
use React\Test\Loop;
$loop = new Loop();
$client = new React\Socket\Connector($loop, ['timeout' => 10]);
$socket = yield $client->connect('ws://localhost:8080');
yield $socket->send('Hello');
Blocking Calls in ReactPHP: Avoid synchronous operations in event handlers. Use:
$this->loop->addTimer(0, function() {
// Non-blocking code here
});
Memory Leaks with ReactPHP:
Always detach connections in onClose:
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
$this->loop->futureTick(function() use ($conn) {
// Additional cleanup
});
}
Guzzle HTTP Client: Updated API calls (v0.4.4+):
// Old (deprecated)
// $client = new GuzzleHttp\Client(['base_uri' => 'https://api.example.com']);
// New
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.example.com']);
Log with ReactPHP Context:
\Log::info("WebSocket event", [
'connection_id' => $conn->resourceId,
'loop_context' => spl_object_hash($this->loop)
]);
Check for Stuck Processes:
Use reactphp CLI tools:
reactphp run -- php public/ws-server.php
ReactPHP Loop Binding: Ensure the loop is passed to all components:
$server = IoServer::factory(
new HttpServer(new WsServer(new MyWebSocket($loop))),
8080
);
SSL/TLS with ReactPHP: Updated SecureServer example:
use Ratchet\Server\SecureServer;
$secureServer = IoServer::factory(
new SecureServer(
new HttpServer(new WsServer(new MyWebSocket($loop))),
'path/to/cert.pem',
'path/to/privkey.pem',
null,
null,
true // Enable ALPN for HTTP/2 compatibility
),
8443
);
Custom ReactPHP Middleware:
use Ratchet\WebSocket\Middleware\MessageComponentMiddleware;
$middleware = new MessageComponentMiddleware(
new MyWebSocket($loop),
function($next) {
return function($conn, $msg) {
// Pre-process message
return $next($conn, $msg);
};
}
);
Metrics with ReactPHP:
Use reactphp-metrics:
use React\Metrics\Metrics;
public function onOpen($conn) {
Metrics::increment('websocket.connections');
}
class StartWebSocketServer extends Command {
public function handle() {
$loop = Factory::create();
$server = app(WebSocketServer::class);
$server->run();
$loop->run();
How can I help you explore Laravel packages today?