Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ratchetphp Laravel Package

plesk/ratchetphp

RatchetPHP is a PHP WebSocket library for building real-time, bidirectional applications. This Plesk-maintained package provides server and client components for event-driven messaging, making it easier to add live updates and persistent connections to PHP projects.

Deep Wiki
Context7

Getting Started

plesk/ratchetphp is a maintained fork of cboden/ratchet (same Ratchet\ namespace, MIT), so upstream docs at socketo.me and the original GitHub examples apply verbatim. The fork's value is PHP 8.x compatibility patches, not new features.

composer require plesk/ratchetphp

First place to look is vendor/plesk/ratchetphp/src/Ratchet/: read MessageComponentInterface.php (four methods: onOpen, onMessage, onClose, onError), then Server/IoServer.php, Http/HttpServer.php, and WebSocket/WsServer.php. The examples/ directory has a runnable echo server and a chat server — start there.

Minimal server:

<?php
require __DIR__.'/vendor/autoload.php';

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class EchoServer implements MessageComponentInterface {
    protected SplObjectStorage $clients;
    public function __construct() { $this->clients = new SplObjectStorage; }
    public function onOpen(ConnectionInterface $conn)   { $this->clients->attach($conn); }
    public function onMessage(ConnectionInterface $conn, $msg) {
        foreach ($this->clients as $c) { $c->send($msg); }
    }
    public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); }
    public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); }
}

IoServer::factory(
    new HttpServer(new WsServer(new EchoServer)),
    8080
)->run();

Run it with php server.php — not through a web server. Then connect from the browser: new WebSocket('ws://localhost:8080').

First real use case for a Laravel dev: a long-running sidecar process that pushes events (notifications, presence, live dashboard updates) to browsers, while Laravel itself stays a stateless HTTP app. Everything below assumes that shape.

Implementation Patterns

Stack the decorators, don't reinvent them. The default composition is HttpServer(WsServer($component)). Insert middleware in that chain: SessionProvider (Symfony sessions), OriginCheck, or a router for multiple endpoints. You can wrap your component in additional decorators that inspect $conn->httpRequest during onOpen before delegating.

Keep components as plain services. Inject dependencies via constructor; store per-connection state on $conn (it accepts dynamic properties) or in an SplObjectStorage keyed by connection. $conn->resourceId exists but treat it as an opaque key, not something to parse.

Rooms/topics, the standard shape:

public function onOpen(ConnectionInterface $conn): void {
    $this->clients->attach($conn);
    // validate auth here; close immediately if invalid
}

public function onMessage(ConnectionInterface $from, $raw): void {
    $msg = json_decode($raw, true);
    if (!is_array($msg) || !isset($msg['room'])) { return; }

    $room = $this->rooms[$msg['room']] ??= new SplObjectStorage;
    $room->attach($from);

    foreach ($room as $client) {
        if ($client !== $from) { $client->send(json_encode($msg)); }
    }
}

public function onClose(ConnectionInterface $conn): void {
    $this->clients->detach($conn);
    foreach ($this->rooms as $room) { $room->detach($conn); }
}

Run it as an Artisan command under Supervisor, so php artisan ws:serve shares config, DI, and logging with the rest of the app:

public function handle(): int {
    $loop = React\EventLoop\Factory::create();
    $app  = new NotificationServer($loop, app(LoggerInterface::class));

    $socket = new React\Socket\SocketServer(
        sprintf('0.0.0.0:%d', $this->option('port')), [], $loop
    );
    $server = new IoServer(new HttpServer(new WsServer($app)), $socket, $loop);

    $loop->addSignal(SIGTERM, fn() => $loop->stop()); // graceful shutdown
    $loop->run();
    return 0;
}

Note the direct SocketServer + IoServer construction — IoServer::factory() creates its own loop, and you need the loop reference for timers and signals.

Cross-process push is the hard part. Laravel HTTP workers cannot call $conn->send() on connections living in another process. The durable pattern: Laravel publishes to Redis (or a DB queue, or SNS); the WebSocket process bridges that into the loop. Use clue/redis-react and $loop->addReadStream() on the Redis socket, or fall back to $loop->addPeriodicTimer(0.25, fn() => drainQueue()). Never attempt to hit the WebSocket process synchronously from a controller.

Auth on connect. The browser WebSocket API cannot set custom headers. Use one of: a signed URL (URL::temporarySignedRoute), a session cookie (works same-origin; SessionProvider can hydrate it), or a first-message handshake. Validate inside onOpen and call $conn->close() immediately on failure — the HTTP upgrade has already completed by then, so rejection is a fast close, not a 401.

Keepalive via the loop:

$ws = new WsServer($app);
$ws->enableKeepAlive($loop, 30);

Test the component, not the socket. MessageComponentInterface takes an interface you can mock trivially; write PHPUnit tests that pass a fake ConnectionInterface with a captured send() buffer. Don't spin up real sockets in unit tests.

Gotchas and Tips

  • It is a daemon, not an FPM script. Hitting ->run() from an HTTP request hangs an FPM worker forever. If you see requests time out after the deploy, someone wired a controller into the server.
  • One blocking call freezes every connection. A synchronous DB::select, file_get_contents, or sleep() inside onMessage stalls the entire event loop. Offload I/O to async clients (clue/reactphp-*) or push work to Laravel's queue and deliver the result back through Redis pub/sub.
  • State is memory-only. A restart drops every socket, and any per-connection state is gone. Deploy with rolling restarts and instruct clients to reconnect with exponential backoff. Persist anything that must survive the process.
  • Your exceptions aren't caught for you. Ratchet calls onError for internal socket/parser failures, but an exception thrown from your onMessage can tear down the connection or crash the loop. Wrap every handler body in try/catch and close the offending connection explicitly.
  • Leaks accumulate silently. Every onClose must detach from SplObjectStorage collections, room arrays, timers, and any registry you keep. Add a $loop->addPeriodicTimer(60, ...) that logs count($clients) and memory_get_usage() — unbounded growth here is the classic failure mode after a few days of uptime.
  • No built-in backpressure. $conn->send() writes straight into the React stream buffer. Broadcasting large payloads to slow clients balloons memory. Cap payload sizes yourself (strlen($msg) > N → close), drop slow consumers, and keep the underlying React connection reference if you want to honor the drain event.
  • Validate Origin manually. CORS does not protect WebSocket handshakes, so a malicious page can open a connection to your server with the victim's cookies. Check $conn->httpRequest->getHeaderLine('Origin') in onOpen (or use Ratchet\Http\OriginCheck) and allowlist explicitly.
  • Reverse-proxy config is mandatory. Behind nginx/Plesk you need proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade, proxy_set_header Connection "upgrade", and a long proxy_read_timeout. Without the upgrade headers the handshake 400s. The peer address will be the proxy — read X-Forwarded-For from $conn->httpRequest, not the socket.
  • Bind address matters. 0.0.0.0 vs 127.0.0.1 is the difference between working and "connection refused" through Docker/containers. Verify with ss -ltnp | grep 8080.
  • Fork maintenance expectations. The package is Plesk-maintained with a small audience (~18 stars). Before upgrading, diff the new tag against cboden/ratchet — fixes are usually PHP 8.2+ deprecation patches, not upstream features. Pin the version in composer.json and check that Composer isn't also pulling cboden/ratchet transitively (the fork should replace it; if you see duplicate Ratchet\ classes, add an explicit conflict or top-level require).
  • Debuggability is poor out of the box. Run in the foreground with Monolog to stderr while developing; log $conn->resourceId on every open/close/message so you can correlate client reports with server logs. Under Supervisor, stopsignal=TERM plus the SIGTERM handler above gives clean shutdown instead of Supervisor's SIGKILL after stopwaitsecs.
  • No idle timeout by default. React sockets stay open indefinitely. Add your own periodic sweep that closes connections that haven't completed a handshake or sent a ping in N seconds; enableKeepAlive() handles ping/pong but tune the interval — aggressive values flap mobile clients.
  • WAMP if you want pub/sub semantics out of the box. Ratchet\Wamp\WampServer gives you topics and RPC without building rooms yourself, at the cost of requiring a WAMP client library on the frontend. For a single Laravel app with simple rooms, hand-rolled is usually less friction.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource