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

Phpsocket.io Laravel Package

workerman/phpsocket.io

PHP Socket.IO server built on Workerman for real-time WebSocket apps. Supports event handling, rooms, namespaces, and Socket.IO client compatibility, offering a practical PHP alternative to Node.js for bidirectional communication.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and run it as a standalone CLI process — this is not something you mount inside Laravel's HTTP stack.

composer require workerman/phpsocket.io

Pick your track deliberately. A bare require resolves to the latest stable tag; the 3.0 line is currently beta and Composer will not select it for you. Check what you actually got, and opt in explicitly if you want the beta:

composer show workerman/phpsocket.io          # confirm the resolved version

# only if you want the 3.0 beta line (see Gotchas before you do):
composer require workerman/phpsocket.io:"^3.0@beta"

Minimal server (socket-server.php at the project root, next to vendor/):

<?php
use Workerman\Worker;
use PHPSocketIO\SocketIO;

require_once __DIR__ . '/vendor/autoload.php';

$io = new SocketIO(2021);

$io->on('connection', function ($socket) use ($io) {
    $socket->on('chat message', function ($msg) use ($io, $socket) {
        $io->emit('chat message', $msg);          // everyone
        $socket->broadcast->emit('typing', false); // everyone but sender
    });
});

Worker::runAll();

Run it:

php socket-server.php start      # foreground, logs to console — use this while developing
php socket-server.php start -d   # daemonized
php socket-server.php reload     # graceful reload after code changes

First use case to validate end-to-end: a broadcast counter or chat, because it exercises connection, custom events, and emit in ~20 lines.

Where to look first:

  1. examples/ in the repo — chat, rooms, namespaces, SSL, attach() to an existing Worker are all demonstrated there.
  2. The socket.io 1.x client/server API docs, not the current site. The PHPSocketIO API mirrors socket.io 1.x ($io->on('connection'), $socket->join, $socket->to(...)->emit). Nothing in the v3.0.0-beta.2 notes indicates a protocol bump, so this guidance stands — but re-confirm against the beta.1 changelog before betting a frontend on it.
  3. SocketIO.php / Socket.php for the actual method surface (origins(), of(), attach()).
  4. tests/ — as of beta.2, RFC6455.php (raw frame parsing: length tiers, masking, fragmentation, ping/pong/close) has real coverage. It's now the fastest way to understand how bytes actually reach your handlers.

Implementation Patterns

Rooms as the default fan-out primitive. Don't hand-roll connection arrays; rooms are the idiomatic grouping.

$socket->on('join', function ($room) use ($socket) {
    $socket->join($room);
    $socket->to($room)->emit('system', "{$socket->id} joined");
});

$socket->on('disconnect', function () use ($socket, $io) {
    $io->to('presence')->emit('left', $socket->id);
});

Namespaces for separation of concerns — /chat, /notifications, /admin — so a mobile client doesn't receive admin traffic:

$chat = $io->of('/chat');
$chat->on('connection', function ($socket) { /* ... */ });

Auth via the handshake query string. socket.io 1.x has no auth payload, so pass the token as a query param and validate in the connection handler:

$io->on('connection', function ($socket) use ($io) {
    $token = $socket->handshake['query']['token'] ?? null;
    $user  = $token ? User::findByApiToken($token) : null;  // if you reuse Laravel's container, bootstrap it first
    if (!$user) {
        $socket->emit('unauthorized', 'Bad token');
        return $socket->disconnect(true);
    }
    $socket->join("user:{$user->id}");
});

Cache the resolved user on the socket ($socket->user = $user;) and read it in later handlers rather than re-querying per event.

Pushing from Laravel (HTTP) into the socket server. The socket server is a separate process; you cannot call $io->emit() from a controller. Two common patterns:

  • workerman/channel — start a Channel\Server, have the socket process subscribe, and publish from the web app over a local TCP socket.
  • Redis pub/sub — subscribe in workerStart, publish from controllers/queues.
$io->on('workerStart', function () use ($io) {
    $channel = new Channel\Client;
    $channel->on('broadcast', function ($data) use ($io) {
        $io->to($data['room'])->emit($data['event'], $data['payload']);
    });
});

Scheduled pushes belong in workerStart with Workerman\Timer, never sleep():

$io->on('workerStart', function () use ($io) {
    Timer::add(30, function () use ($io) {
        $io->emit('tick', ['t' => time()]);
    });
});

Acknowledgements and binary work as in socket.io: $socket->on('ev', function ($data, $ack) { $ack(['ok' => true]); }), and server-side $socket->emit('ev', $data, function ($answer) { ... }).

Reuse an existing Workerman worker (e.g., you already run one for queues) via $io->attach($worker) instead of opening a second port/process.

TLS: terminate at nginx and proxy to ws://127.0.0.1:2021, or do it in Workerman — pass an SSL context as the second constructor arg and set $io->worker->transport = 'ssl' inside workerStart.

Gotchas and Tips

  • Client version is the #1 breakage. This implements the socket.io 1.x/Engine.IO v3 protocol. socket.io-client 2.x usually works; 3.x and 4.x will not connect (EIO=4). Pin the frontend client and pin the PHP package; the release cadence is sparse, so treat upgrades as a deliberate test.
  • The 3.0 line is beta and has breaking changes you didn't see here. v3.0.0-beta.2 explicitly carries over the breaking changes from beta.1 — "no new breaking changes beyond what beta.1 already introduced" is not the same as "no breaking changes." Read the beta.1 release notes / diff before upgrading, budget for it, and keep a locked rollback (composer require workerman/phpsocket.io:<old-version>) ready. Don't let @beta float in production; pin the exact beta.
  • If you're on PHP 8 and see handshake-time fatals, this is your fix. Beta.2 corrects Engine\Protocols\WebSocket::input() reading TcpConnection::$maxPackageSize as a static property when it's an instance property — a fatal on PHP 8 whenever a WebSocket handshake's headers didn't arrive in a single read (large Cookie/Authorization headers, or a proxy that splits the upgrade). On PHP 7.x the same bug silently made the check always-true, i.e. the max-package-size guard wasn't actually guarding anything. If you have oversized-payload mitigations in place, retest them on beta.2.
  • JSONP fallback was outright broken in beta.1. A PollingJsonp::headers() signature mismatch threw a TypeError for any JSONP client POSTing data. Fixed in beta.2. If you support legacy clients via the ?j=N transport (or just never disabled JSONP), you want this release.
  • Package-size limits are per connection, not static. Tune TcpConnection::$defaultMaxPackageSize (the static default) or override $connection->maxPackageSize per connection — and if you've copied the old TcpConnection::$maxPackageSize access pattern into your own code, you've copied the bug.
  • Origin checks are enforced. Cross-domain clients get a 400 until you call $io->origins('https://app.example.com') or, in dev, $io->origins('*:*'). Cookie-based auth also needs withCredentials on the client plus an explicit origin (not *).
  • Rooms and $io->emit() are per-process. If you set $io->worker->count = 4, each process has its own room table and its own client list — broadcasts silently miss users on other workers. Keep count = 1 until you've wired Channel/Redis fan-out.
  • The event loop is single-threaded per process. Any synchronous HTTP call, sleep, heavy query, or big file_get_contents inside a handler stalls every connected client. Push slow work to a queue and emit the result asynchronously.
  • Long-running process = memory leaks. Anything you store keyed by $socket->id must be removed in on('disconnect'), and don't capture $socket in closures that outlive the connection (timer callbacks especially — check isConnected/guard before emitting).
  • No built-in HTTP push API and no admin dashboard. Plan for a small internal listener (Channel, a separate text-protocol Worker, or Redis) from day one; retrofitting it is painful.
  • Run it as a supervised daemon. Under systemd/supervisor with php socket-server.php start, logs to Worker::$stdoutFile / Worker::$logFile. Debugging is far easier in the foreground — -d hides the stack traces you actually need.
  • Blocking errors kill a worker. Wrap handler bodies in try/catch and log; an uncaught exception in a callback can take down the process and drop all its connections.
  • connection closures need use ($io) — forgetting it is the most common "why does nothing emit" bug.
  • $socket->disconnect() vs disconnect(true): the latter closes the underlying transport immediately; use it for rejected handshakes, and prefer rooms/emit('unauthorized') first so the client sees a reason.
  • Reverse proxies need Upgrade/Connection headers and a long proxy_read_timeout; load balancers need sticky sessions if you ever scale to multiple hosts (or force transports: ['websocket'] client-side to avoid polling upgrades entirely).
  • Windows is not a realistic target — Workerman's Windows path is single-process/limited and blocks on pcntl/posix; develop on Linux or WSL.
  • Verify $socket->handshake in a debug emit on your first integration; its shape (headers, query, address, time) is the fastest way to confirm auth and proxy headers are arriving as expected — and it's exactly the code path beta.2 fixed.
  • Subclassers: re-test after the parent:: refactor. Several call_user_func([get_parent_class(...), 'method'], ...) sites were rewritten to plain parent::method(...) calls. Claimed behaviorally identical, but if you've forked or extended Engine\Protocols\* / RFC6455, your overrides now dispatch through a different (more conventional) path — one smoke test is cheap insurance.
  • Static analysis is now usable as your own safety net. composer lint runs phpstan analyse (level 6, ramped from 0) alongside phpcs. If you vendor-patch or extend internals, running the same command locally catches a real class of signature bugs — that's how the PollingJsonp::headers() TypeError and a Decoder.php out-of-bounds read (PHP-warning-on-some-configs) were found. Note that level 6 is not level max; untyped array shapes are still unverified.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin