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

Stomp Php Laravel Package

fusesource/stomp-php

A lightweight PHP client for the STOMP messaging protocol. Connect to STOMP-compatible brokers, send and receive messages, and subscribe to destinations with a simple object-oriented API. Useful for integrating PHP applications with message queues.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require fusesource/stomp-php

The upstream package was published in the PHP 5 era. If Composer can't resolve it (or you get a 404 on Packagist), pull the maintained fork directly:

"repositories": [{ "type": "vcs", "url": "https://github.com/dejanb/stomp-php" }],
"require": { "dejanb/stomp-php": "dev-master" }

Either way the namespace is FuseSource\Stomp\*, so application code stays identical. Pin the exact version or commit SHA in composer.lock — treat this dependency as vendored code you own.

First look — what's actually in the box

Class Purpose
FuseSource\Stomp\Stomp The client: connect, send, subscribe, readFrame, ack, transactions
FuseSource\Stomp\Connection Socket/SSL transport, read timeout, URI parsing
FuseSource\Stomp\Frame Raw STOMP frame (command, headers, body)
FuseSource\Stomp\Message\Bytes / Map Body wrappers for binary vs. map payloads
FuseSource\Stomp\Exception\StompException Everything that goes wrong at the protocol layer

There is no service provider, facade, config file, or queue connector. You are looking at a thin protocol client — everything Laravel-shaped is code you write.

Smoke test (no Laravel yet) — run it from tinker or a throwaway script to prove your broker is reachable before wiring anything up:

use FuseSource\Stomp\Stomp;
use FuseSource\Stomp\Message\Bytes;

$stomp = new Stomp('tcp://localhost:61613');
$stomp->connect('admin', 'admin');
$stomp->send('/queue/demo', new Bytes(json_encode(['hello' => 'world'])), [
    'persistent' => 'true',
]);
$stomp->subscribe('/queue/demo', ['ack' => 'client']);
$frame = $stomp->readFrame();
if ($frame) {
    $stomp->ack($frame);
    dump($frame->body, $frame->headers);
}
$stomp->disconnect();

If that round-trips, the rest is Laravel plumbing. If it hangs, the broker isn't speaking the STOMP version your client defaults to — check the broker's STOMP transport is enabled and listening on the expected port (61613 is typical for ActiveMQ).

First real use case. This package is not a Laravel queue driver — it's a raw STOMP client. Reach for it when you must talk to an external broker that Laravel can't: ActiveMQ Artemis, a JMS/Java system publishing to /queue/, RabbitMQ with the STOMP plugin enabled, or an existing enterprise integration bus. For greenfield internal queues, use redis or sqs and save yourself the work.


Implementation Patterns

1. Config + singleton binding

config/stomp.php:

return [
    'uri'      => env('STOMP_URI', 'tcp://localhost:61613'),
    'username' => env('STOMP_USER'),
    'password' => env('STOMP_PASS'),
    'timeout'  => (int) env('STOMP_READ_TIMEOUT', 5),
];

app/Providers/StompServiceProvider.php:

public function register(): void
{
    $this->app->singleton(Stomp::class, function ($app) {
        $cfg = $app['config']['stomp'];
        $stomp = new Stomp($cfg['uri']);
        $stomp->setReadTimeout($cfg['timeout']);
        $stomp->connect($cfg['username'], $cfg['password']);
        return $stomp;
    });
}

Bind it as a singleton so a queue worker reuses one connection across jobs rather than reconnecting per message. But for HTTP requests under FPM, do not let a request-scoped instance outlive the request without a reset — a dropped socket poisons every subsequent call. Prefer publishing from a queued job or a short-lived connection.

2. A thin publisher service

Wrap the client so controllers and listeners never see STOMP types:

class StompPublisher
{
    public function __construct(private Stomp $stomp) {}

    public function publish(string $destination, array $payload, array $headers = []): void
    {
        $body = json_encode($payload, JSON_THROW_ON_ERROR);

        $this->withReconnect(function () use ($destination, $body, $headers) {
            $this->stomp->send(
                $destination,
                new Bytes($body),
                $headers + [
                    'persistent'   => 'true',
                    'content-type' => 'application/json',
                ]
            );
        });
    }

    private function withReconnect(callable $op): void
    {
        try {
            $op();
        } catch (StompException $e) {
            // One retry on a fresh connection — a stale socket is the common case.
            $this->reconnect();
            $op();
        }
    }
}

Define an interface (MessagePublisher, MessageConsumer) in your app and bind the STOMP implementation to it. This is the single most valuable pattern here: it turns a dormant dependency into a swappable detail.

3. Consumer as an Artisan command

Run the consumer as a long-lived, supervisor-managed command — not inside a web request:

class ConsumeStompCommand extends Command
{
    protected $signature = 'stomp:consume {destination}';

    public function handle(StompPublisher $publisher): int
    {
        $stomp = app(Stomp::class);
        $stomp->subscribe($this->argument('destination'), [
            'ack'            => 'client',
            'activemq.prefetchSize' => 10,
        ]);

        while ($this->running) {
            $frame = $stomp->readFrame();
            if (! $frame) {
                continue; // read timeout, no message
            }

            try {
                $this->process(json_decode($frame->body, true, 512, JSON_THROW_ON_ERROR));
                $stomp->ack($frame);
            } catch (Throwable $e) {
                // nack / route to DLQ or leave unacked for redelivery
                report($e);
            }
        }

        $stomp->unsubscribe($this->argument('destination'));
        $stomp->disconnect();
        return self::SUCCESS;
    }
}

Handle SIGTERM to break the loop cleanly and disconnect — long-lived sockets plus container restarts create connection storms if workers don't exit gracefully.

4. Transactions for grouped work

When a unit of work emits several messages that must land together, use STOMP transactions:

$stomp->begin('tx-1');
try {
    foreach ($events as $dest => $payload) {
        $stomp->send($dest, new Bytes(json_encode($payload)), ['transaction' => 'tx-1']);
    }
    $stomp->commit('tx-1');
} catch (Throwable $e) {
    $stomp->abort('tx-1');
    throw $e;
}

Transactions are broker-scoped, not database-scoped. They do not participate in an Eloquent DB transaction — wrap the dispatch so you tolerate an event being published before or after a rollback, or publish only after commit via DB::afterCommit().

5. Bridging to Laravel's event system

A clean day-to-day integration is to use STOMP as a transport behind Laravel events:

Event::listen(OrderShipped::class, function (OrderShipped $event) use ($publisher) {
    $publisher->publish('/topic/orders', [
        'type' => 'order.shipped',
        'id'   => $event->order->id,
    ]);
});

Publish after the DB transaction commits (ShouldDispatchAfterCommit or an explicit DB::afterCommit()), and include a message-id/correlation header so downstream consumers can dedupe.

6. Topics vs. queues

Use /queue/... when you need load-balanced consumption (one worker gets each message) and /topic/... for fan-out to every subscriber. This is a broker convention, not a client setting — get the destination prefix right or you'll silently get broadcast instead of competing consumers.


Gotchas and Tips

It is not a Laravel queue driver. There is no Queue::push() bridge. Do not try to fake one by registering a custom connector unless you're prepared to own retries, failed-job persistence, backoff, and Horizon compatibility yourself. Use an internal publisher/consumer interface and call it directly from jobs, listeners, or commands.

PHP version support is a live risk. Assume 5.x/7.x-era code until proven otherwise. Test on your exact PHP 8.x minor version, check PSR-4 autoloading, and verify ext-sockets is installed. If it fails on modern PHP, that's your fork trigger — budget for it up front rather than during an incident.

No automatic reconnect or heartbeats. The client will happily sit on a dead socket. Add your own reconnect wrapper, set an explicit read timeout, and enable broker-side heartbeats where the protocol level supports them (STOMP 1.1+). Zombie connections show up as consumers that "never get messages" while the broker thinks they're healthy.

Ack semantics matter. ack => 'client' means you must ack() each frame or the broker redelivers it. ack => 'auto' risks message loss if your process dies mid-handler. Default to client acks and process idempotently — at-least-once is the realistic guarantee here, not exactly-once.

readFrame() returns null/false on timeout, not an exception. Code that does while ($frame = readFrame()) will busy-loop. Check the return explicitly and add a small usleep() or rely on the read timeout blocking behavior — otherwise a quiet queue pegs a CPU core.

Octane/Swoole will bite you. A singleton Stomp instance survives across requests. A dropped socket mid-worker means every subsequent request fails until the worker recycles. Either register a reset hook that reconnects, or create connections per-request for low-volume publishing.

Persistent messages need the header, not a setting. Set 'persistent' => 'true' on the frame — there's no client-level default. Forgetting this means your messages vanish on broker restart, and you won't notice until you need them.

Debugging is by raw frames. When something's off, dump $frame->command, $frame->headers, and $frame->body directly. Enable broker-side logging (ActiveMQ's TRACE level) to see if the frame arrived at all — that distinguishes a client serialization bug from a routing/destination problem. There is no Laravel log channel integration; add your own logging around publish/consume calls.

Content types aren't enforced. Bytes vs. Map changes the wire format. Stick to Bytes with JSON for cross-language interoperability — Java/JMS consumers will otherwise see opaque bytes.

No security disclosure process and no CI. There's no CVE feed, no dependabot signal, no maintainer response. Run composer audit on your own schedule, vendor the source if the path is critical, and document a fork-and-patch policy with a named owner. "It works today" is not the risk — "nobody patches it in eighteen months" is.

Prefer the maintained fork. stomp-php/stomp-php is API-compatible and reduces long-term risk materially. Use this fork only if a hard constraint prevents it, and note the decision in your ADRs.

Extension points are thin by design. There's no middleware, no interceptors, no connection pool. The realistic extension points are: your wrapper service, your reconnect logic, and a fork of Connection if you need custom transport behavior. Plan for those rather than expecting plugin hooks.

Validate against the real broker, not a mock. STOMP is deceptively simple; the failures live in destination naming, ack modes, transaction scoping, and heartbeats. Run a contract test against the exact broker version and STOMP protocol level (1.0/1.1/1.2) in staging before committing to production.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment