wisembly/elephant.io
Elephant.io is a PHP WebSocket client for talking to Socket.IO servers. It offers a simple API to emit events and listen for responses, making real-time communication easy to add to PHP applications.
Install
composer require wisembly/elephant.io
This package is archived and the last tagged release is from May 2017. It still installs and runs on modern PHP, but treat it as a frozen dependency: no security patches, no Socket.IO v3/v4 support, and no PHP 8 signature fixes are coming.
Where to look first
ElephantIO\Client — the façade you actually talk to (initialize(), emit(), read(), close(), getEngine()).ElephantIO\Engine\SocketIO\Version1X / Version2X — engines that speak the Socket.IO handshake and framing. Pick the one matching the server's Socket.IO version, not your PHP version.ElephantIO\Engine\WebSocket\WebSocket — for a plain RFC 6455 endpoint that is not Socket.IO.ElephantIO\Exception\SocketException — the only exception type you will realistically catch.First working example — emit an event to a local Socket.IO 2.x server:
use ElephantIO\Client;
use ElephantIO\Engine\SocketIO\Version2X;
$client = new Client(new Version2X('http://127.0.0.1:3000', [
'timeout' => 5,
]));
$client->initialize();
$client->emit('order.created', ['id' => 42, 'total' => 19.99]);
$client->close();
That is the 80% use case: a Laravel request or job wants to tell a Node-side Socket.IO process that something happened. There is no pub/sub layer, no acknowledgment, and no server-side API — you are just a very small WebSocket client.
1. Never call it inline in a web request. The handshake involves a blocking GET /socket.io/... plus a WebSocket upgrade. Wrap the client in a queued job or a listener implementing ShouldQueue so a slow/unreachable Socket.IO server never slows your users.
class BroadcastSocketEvent implements ShouldQueue
{
public function __construct(
public string $event,
public array $payload,
) {}
public function handle(): void
{
$client = new Client(new Version2X(config('elephant.url'), [
'timeout' => config('elephant.timeout', 5),
]));
$client->initialize();
$client->emit($this->event, $this->payload);
$client->close();
}
}
2. Bind a thin service into the container. Don't sprinkle new Client(...) across the codebase; a SocketIoEmitter class with one emit(string $event, array $payload) method is the right granularity for testing and for swapping the implementation later.
3. Version-match the engine explicitly. Version1X for Socket.IO 1.x, Version2X for 2.x, and (legacy) Version0X. There is no Version3X/Version4X — the package was archived before those protocols existed. If you are on Socket.IO 4, you cannot use this library against it without protocol shims.
4. Namespaces. Client::emit($event, $data, $namespace) sets the nsp in the CONNECT packet. The server must already accept that namespace; the client does not negotiate it.
5. Plain WebSocket endpoints. Use WebSocket engine and drive frames via $client->getEngine() and $client->read(). There is no emit semantics here — you are sending raw text/binary frames.
6. Long-running consumer / bridging worker. Blocking read loop with backoff:
while (true) {
try {
$client = new Client(new WebSocket($url));
$client->initialize();
while ($frame = $client->read()) {
$this->dispatchDownstream($frame);
}
} catch (Throwable $e) {
Log::warning('elephant.io disconnected', ['err' => $e->getMessage()]);
sleep(5);
} finally {
try { $client->close(); } catch (Throwable) {}
}
}
Run it under Supervisor (or a dedicated Horizon queue with a single worker) — not inside a normal php artisan serve request.
7. Config. Add config/elephant.php with url, engine, timeout, and a boolean for SSL verification. Map it in AppServiceProvider to the emitter binding so production credentials never live in code.
8. Testing. Point the emitter at a local Socket.IO server in Docker Compose and assert server-side receipt of the event. The package ships no in-memory transport, no mock client, and no way to assert an emit() without a real server.
9. Auth. There is no first-class auth hook. Append query parameters to the URL (https://host:3000/?token=...) or pass http headers through the context option. Neither is guaranteed to survive the WebSocket upgrade on every Socket.IO server.
GET /socket.io/... returns non-2xx or the upgrade stalls, you get a SocketException, often only after the timeout. Verify the Socket.IO version on the server first.read() blocks indefinitely. There is no non-blocking mode, no promise, no future. Every consumer design is a blocking loop; treat it as a dedicated worker process.close() is not tolerant of half-open state. Calling it after a failed initialize() or twice can throw or double-close the socket. Always wrap in try/finally and swallow close errors.emit() is fire-and-forget. No ack, no error if the remote has no listener for the event, no delivery guarantee if the socket dies immediately afterward. Build your own confirmation channel if you need one.timeout is the connect timeout, not a read deadline. A server that accepts TCP and then goes quiet will leave read() hanging past the configured timeout.debug => true writes raw frames to stdout. Fine in a CLI script, catastrophic in a web request. Prefer injecting a psr/log LoggerInterface as the second Client argument.wss://, pass a context via stream_context_create() with proper ssl options. Peer verification is on by default at the PHP layer; disable it explicitly and only for self-signed/internal hosts.http/https map to ws/wss internally. Any path prefix behind a reverse proxy (e.g. /socket.io/) must be in the base URL.Client across queue workers or after pcntl_fork(). One client, one process, one connection, initialize → emit → close.ElephantIO\Engine\EngineInterface to add a new protocol; inject a custom LoggerInterface; use $client->getEngine() to reach the raw socket for anything the engine doesn't expose. Those three are the only seams the library offers.How can I help you explore Laravel packages today?