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

Socket Laravel Package

orchid/socket

Laravel package that adds a WebSocket server to your app, built on Ratchet. Broadcast events over public, private, and presence channels and push real-time updates to Orchid admin screens without relying on external services.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire in one pass.

composer require orchid/socket
php artisan vendor:publish --tag=socket-config   # verify the tag; no docs mean you read the provider

Then set the broadcast driver and the daemon's host/port in .env, and confirm which values the package actually reads by opening the published config file — because it is the documentation.

BROADCAST_DRIVER=socket         # confirm the exact driver key in config/broadcasting.php
SOCKET_HOST=0.0.0.0
SOCKET_PORT=8002

Start the server in its own terminal:

php artisan socket:serve

Where to look first, in this order:

  1. src/SocketServiceProvider.php — what the package registers, which config keys it merges, whether it overrides the broadcast manager.
  2. The socket:serve command class — it reveals the event loop (ReactPHP vs. Workerman vs. Ratchet), signal handling, and all available flags (daemonize, verbose, host/port overrides).
  3. config/socket.php — connection limits, ping/idle timeouts, origin allowlist, auth guard.
  4. The broadcast driver class — this tells you how the HTTP process hands a payload to the daemon (inter-process socket, Redis pub/sub, or file). That single answer determines whether you need Redis and whether the daemon and web nodes must be co-located.

First use case: prove the pipe end-to-end with the smallest possible event. Do not start with presence channels or private auth. Ship one public channel, one listener, and verify the frame in browser devtools.

// app/Events/DeployStatus.php
class DeployStatus implements ShouldBroadcast
{
    public function __construct(public string $status) {}

    public function broadcastOn(): array
    {
        return [new Channel('deploys')];
    }

    public function broadcastAs(): string { return 'deploy.status'; }
}

Frontend with Echo (point wsHost/wsPort at the daemon, not at your app host):

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_SOCKET_KEY,
    wsHost: import.meta.env.VITE_SOCKET_HOST,
    wsPort: 8002,
    forceTLS: false,
    enabledTransports: ['ws'],
    disableStats: true,
});

Echo.channel('deploys').listen('.deploy.status', e => console.log(e.status));

Once that frame arrives, you have validated install, config, driver, protocol, and client compatibility. Everything after that is incremental.

Implementation Patterns

Keep all publishing behind Laravel's broadcasting abstraction. This is the single most important pattern, and it is not optional given the package's maturity signals. Domain code should only ever emit ShouldBroadcast events; nothing outside config/ should reference Orchid\Socket\*. Enforce it in code review — the exit to Reverb or Soketi collapses to a config change only while that rule holds.

Queue the broadcasts, don't block. Laravel queues broadcast events unless they implement ShouldBroadcastNow. For anything triggered by a user request, let the queue worker hand the payload to the daemon; synchronous broadcasting inside a request couples your HTTP latency to the WebSocket daemon's health. Keep ShouldBroadcastNow for cases where ordering matters and volume is tiny (e.g. a terminal status that a queued job might reorder).

Design channels as a public contract before writing features. deploys.{id}, orders.{tenantId}, presence-admin. Once clients subscribe, renaming is a breaking change across two codebases. Write the channel map down — it survives a provider swap.

Private channel auth goes through routes/channels.php like any other broadcaster, but budget for the per-connection callback latency. The daemon must call back into your HTTP app to authorize, which means:

  • The callback URL must be reachable from the daemon host (localhost fine on a single VM; a real internal load balancer otherwise).
  • Your session/guard must be usable from that callback — typically an API guard with a token, since a browser session cookie won't be present on a pusher-js auth request unless you configure authorizer to send credentials.
  • Cache expensive authorization lookups; a reconnect storm after a deploy will hammer this endpoint.

Supervise the process like the infrastructure it is. systemd unit or Supervisor, Restart=always, log to stdout, and a stopwaitsecs generous enough for graceful shutdown. Never run socket:serve in a request lifecycle or under a process manager that recycles instances — the daemon owns connection state, and an instance that dies takes every socket with it.

Front the daemon with the same reverse proxy as the app. nginx needs the upgrade dance and, critically, an idle timeout longer than your ping interval:

location /ws {
    proxy_pass http://127.0.0.1:8002;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
}

Integration tip for Orchid panels specifically: this package's natural home is live admin dashboards — job status, queue depth, audit feeds. Wire Orchid screens to broadcast on domain events and let the panel subscribe, rather than polling the database in a screen's query. That is the lowest-risk, highest-visible-value entry point, and it exercises the whole path with internal users who forgive a hiccup.

Test the daemon, don't just test the events. Unit-testing broadcast() tells you nothing about whether a frame reaches a client. Keep a small Node script using ws in CI, or a Pest test that opens a raw socket, subscribes, dispatches an event, and asserts the frame within a timeout. This is the only regression net you will have.

Gotchas and Tips

The package has zero dependents and no release cadence — pin the exact commit. composer require orchid/socket:dev-master#<hash> or a fork. A floating constraint against a repo with no tags is an unpinned dependency on someone else's master branch. Read the git log before adopting; recent activity is your only maintenance signal.

Verify Pusher protocol fidelity early. The whole frontend portability story rests on stock laravel-echo + pusher-js working unmodified. If the package needs a bespoke client, your exit path just got expensive and you should know that on day one, not month six. Test listen, listenForWhisper, join, here, joining, leaving — and expect some of them to be missing or subtly wrong.

Do not assume toOthers(), broadcastAs(), or dontBroadcastToCurrentUser() behave correctly. toOthers depends on the socket ID being threaded through the payload, which hand-rolled servers routinely drop. Verify with two browser tabs before shipping any "don't notify the actor" UX.

Reconnect semantics are your problem, not the package's. A daemon restart, a deploy, or a proxy idle-timeout kill all look identical to the client. Configure Echo's authorizer so private channels re-auth on reconnect, set pongTimeout/activityTimeout in line with the daemon's ping interval, and test a systemctl restart while a browser is connected. Silent deaths under a too-short ALB idle timeout (default 60s) are the classic failure: the socket looks open, no frames arrive, nobody notices for a week.

WSS through the proxy, not in the daemon. Terminate TLS at nginx/ALB, set forceTLS: true and wssPort: 443 in Echo. Don't try to make the PHP daemon speak TLS — you'll fight certificate reloads on every renewal.

Event-loop blocking is the sharp edge. Any synchronous work inside a message handler — an Eloquent query, an HTTP call, a file write — stalls every connected client, not just the sender. Handlers should do the minimum: validate, push to a queue, return. Also watch RSS over 24h; a daemon that grows steadily is leaking per-connection state and will OOM at a bad hour.

pcntl and posix are required and Windows is out. Confirm the extension list in composer.json against your actual runtime (the container image, not your laptop). Also verify behavior under Octane separately — two long-running PHP runtimes in one deployment is a debugging burden people underestimate, and Octane's worker recycling does not apply to the daemon.

Debugging recipe when frames don't arrive: browser devtools → Network → WS → check the handshake status (401/403 means auth or origin rejection, not a code bug), then Frames for the subscribe payload, then the daemon's stdout for the received message, then whether the event actually fired (Laravel log / Event::fake sanity check), then the broadcast driver bridge. The gap is almost always between step 3 and 4 — the HTTP process never handed the payload to the daemon, usually because the queue worker isn't running or the bridge (Redis/port) is unreachable.

Origin and CORS allowlists trip up staging environments. If the config has an origin check, a preview deployment on a different subdomain will fail the handshake with no useful client-side error. Keep the allowlist in .env and document it.

Keep payloads small and versioned. No models — DTOs or arrays with an explicit shape. Every payload is a contract with clients you cannot atomically deploy alongside. Include a v or type field so a frontend can ignore events it doesn't understand.

Write down who owns this daemon. With no maintainer to escalate to, "it's on fire at 2am" has exactly one answer, and it needs to be a name plus a runbook entry: how to restart, how to check connection count, how to confirm the queue worker is draining. If that name doesn't exist, don't ship it — the exit to Reverb is cheap only while the system is healthy.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core