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

Pheanstalk Bundle Laravel Package

pyrowman/pheanstalk-bundle

A Symfony bundle that integrates the Pheanstalk PHP client, making it easy to configure and use beanstalkd job queues from Symfony services and dependency injection, with bundle configuration and service wiring for Pheanstalk.

View on GitHub
Deep Wiki
Context7

Getting Started

First, set expectations about fit. pyrowman/pheanstalk-bundle is a Symfony bundle — it defines a Bundle class, a DI Extension, and a Configuration tree, none of which Laravel's package discovery loads. You will not composer require this into a Laravel app and get a working Beanstalkd connection. Composer will resolve it, but no service provider registers anything, and if symfony/framework-bundle is a hard require you'll drag foreign framework wiring into your dependency graph.

What you actually leverage is the Pheanstalk client underneath. The bundle is a thin configuration wrapper around pda/pheanstalk. In Laravel, install the client directly:

composer require pda/pheanstalk

Check whether you need it at all. On Laravel ≤ 10, the framework ships a first-party beanstalkd queue driver — set QUEUE_CONNECTION=beanstalkd in .env, populate config/queue.php, and you're done. You never touch Pheanstalk or this bundle. Laravel 11+ removed that driver, so verify your version first; that removal (not this bundle) is the reason a Laravel shop reaches for Pheanstalk directly.

Run the daemon locally (beanstalkd is a separate binary, not a PHP service):

docker run -d -p 11300:11300 --name beanstalkd schickling/beanstalkd

First use case — a five-line producer to prove connectivity before writing any adapter:

use Pheanstalk\Pheanstalk;

$client = Pheanstalk::create('127.0.0.1', 11300);
$client->useTube('emails')->put(json_encode([
    'class'    => SendWelcomeEmail::class,
    'payload'  => ['user_id' => 42],
    'attempts' => 0,
]));

Then reserve and delete the same job from beanstalkd console or a tiny Artisan command. If that round-trips, you have a working transport; everything after is architecture, not integration. Look first at the Pheanstalk README and beanstalkd's protocol doc — the bundle's own docs won't help you here, and its release notes for 1.0.0 are two URLs with no changelog.

Implementation Patterns

Singleton client in a service provider. One Pheanstalk instance per configured server, bound by name, resolving from config/database.php or a dedicated config/beanstalkd.php that mirrors the bundle's Symfony config semantics (server list, host/port/timeout):

$this->app->singleton(Pheanstalk::class, fn () => Pheanstalk::create(
    config('beanstalkd.host'),
    config('beanstalkd.port'),
    config('beanstalkd.timeout'),
));

For multi-server setups (the bundle's main selling point), bind named instances and resolve them explicitly — don't rely on a single global.

Producer service, not raw client in controllers. Wrap the client so application code never references Pheanstalk\*:

class JobPublisher
{
    public function __construct(private Pheanstalk $client) {}

    public function push(string $tube, string $jobClass, array $payload, int $delay = 0): void
    {
        $this->client
            ->useTube($tube)
            ->put(json_encode(compact('jobClass', 'payload')), Pheanstalk::DEFAULT_PRIORITY, $delay, ttr: 60);
    }
}

This interface is your insurance policy — it makes removal a config change if the package is abandoned, which matters for a single-maintainer, 2019-last-release dependency.

Worker loop as an Artisan command. Use reserveWithTimeout, not blocking reserve, so the process can respect SIGTERM during deploys:

while ($this->running) {
    $job = $this->client->watch('emails')->reserveWithTimeout(5);

    if (! $job) {
        continue;
    }

    try {
        $this->dispatch(json_decode($job->getData(), true, flags: JSON_THROW_ON_ERROR));
        $this->client->delete($job);
    } catch (Throwable $e) {
        $job->getData(); // log it — the body is gone once released/buried
        $this->client->release($job, priority: 100, delay: 30);
    }
}

Supervise this with Horizon-style scaling logic of your own, or one Supervisor program per tube.

Tubes are your queue names. Encode the mapping explicitly (emails, webhooks, images) in config so tube names are referenced by constant, never by string literal scattered through the code. Beanstalkd's tube model has no registry — a misspelled tube is a silently starved queue, not an error.

Priorities: lower number = higher priority. Beanstalkd's range is 0 (urgent) to 2^32-1 (idle). Don't map Laravel's onQueue('high') intuition onto raw numbers without a config map — an inverted assumption here is easy to make and hard to notice.

Delays and TTR. put(..., delay: 300, ttr: 60) means "not visible for 5 minutes, must complete in 60 seconds." TTR is a visibility deadline, not a timeout — if the worker exceeds it, the job is reserved again, not killed. Size TTR generously above your p99 job duration.

Envelope format. Beanstalkd stores opaque bytes (default max 65,535 bytes per job). Serialize a {jobClass, payload, attempts} envelope so the consumer can reconstruct the handler and track retries — Laravel's own job format is a good model to copy.

Integration tip: don't port the Symfony bundle's config surface. If someone already has a Symfony YAML config referencing this bundle, transcribe the host/port/timeout/connection-list values into Laravel config manually. Do not attempt to bridge the Symfony DI container into Laravel.

Gotchas and Tips

Namespaces and discovery are the first wall. composer require pyrowman/pheanstalk-bundle in a Laravel app produces no artisan vendor:publish, no bound services, no config. If symfony/framework-bundle is a hard requirement (unverified — no composer.json in the docs), it's near-disqualifying: installing a foreign framework's wiring to obtain a socket client is a bad trade. Audit with composer why symfony/* before committing.

Pin exactly. Use 1.0.0, not ^1.0, until you've read the actual composer.json and the closed 1.0.0 milestone. The release notes contain no changelog, migration guide, or breaking-change list, so you can't reason about upgrade cost later.

The package is stale and single-author. Last release 2019-11-29, zero dependents, one maintainer. Check PHP 8.2/8.3 compatibility against the resolved Pheanstalk major before pinning anything in production.

delete() or the job comes back. Beanstalkd has no ack; a job you don't delete, release, or bury is re-reserved after TTR. Every consumer path must terminate the job explicitly, including error paths.

Buried jobs are silent data loss. After the beanstalkd retry limit, jobs move to a buried state — not a dead-letter queue, not an exception. They're invisible unless you poll stats-tube. Add a scheduled check:

beanstalkd console → stats-tube emails → current-jobs-buried

Alert on non-zero for any user-facing tube, and provide a kick-job/kick command for recovery.

TTR vs. processing time is the #1 production surprise. A job that runs longer than its TTR gets handed to a second worker; you now have two executions of the same job. If your job is non-idempotent, this is a duplicate side-effect bug, not a queue bug. Design handlers idempotent from day one.

Priority inversion. 0 is the highest priority in beanstalkd. If you've internalized "high = 10" from other systems, everything will run backwards and you'll debug it for an afternoon.

Tube typos starve silently. Assert at boot that expected tubes exist (stats-tube on startup, or fail fast on connection). Beanstalkd will happily create a new tube for a typo and never deliver to it.

Daemon availability is on you. Pheanstalk throws on connect failure; your worker loop will crash-loop unless you add backoff. Beanstalkd is single-node, no replication, no durable persistence beyond the optional binlog — design for job loss on host failure. This is the same ceiling you'd hit with any beanstalkd setup, and it's why beanstalkd is for ephemeral work (email fan-out, webhooks, image thumbnails), not billing events.

Byte cap per job. 65,535 bytes by default. Don't stuff an image or a large PDF into the payload — store it (S3 / disk) and enqueue the reference.

Extension points are yours to build, not the bundle's. The useful abstraction is an interface around "publish job" and "consume job" so beanstalkd is one implementation among possible ones. Wrap Pheanstalk, expose your own contracts, and keep Pheanstalk\* out of application code — that's the only real exit ramp this package offers, and it costs one class file.

Scaling caveat before you pilot. Beanstalkd scales by adding daemons you shard across, not by replication. A single-node pilot will run fine and quietly hide the fact that you can't spread load or fail over. Decide up front whether one box is sufficient for the workload's whole lifetime — if not, this is the wrong transport.

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.
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
noith/settings
kda/laravel-package