php-programmist/pheanstalk-bundle
A Symfony bundle that integrates Pheanstalk, the PHP client for the beanstalkd work queue, wiring it into the service container so you can configure connections and inject a ready-to-use job queue client into your application services.
This is a Symfony bundle (LeezyPheanstalkBundle) that wires the pda/pheanstalk client into a Symfony container. It is not a Laravel package — there is no service provider, facade, or queue driver. If you are working in Laravel, the honest first step is usually to install pda/pheanstalk directly and write a small binding yourself; use this bundle only if you are in a Symfony app or deliberately reusing its config/service conventions.
Symfony setup (the intended path):
composer require php-programmist/pheanstalk-bundle
Register the bundle in config/bundles.php:
Leezy\PheanstalkBundle\LeezyPheanstalkBundle::class => ['all' => true],
Minimal config in config/packages/leezy_pheanstalk.yaml:
leezy_pheanstalk:
connections:
default:
servers:
- 127.0.0.1:11300
default_connection: default
Where to look first:
vendor/php-programmist/pheanstalk-bundle/ — inspect DependencyInjection/Configuration.php for the exact config tree (servers, connection aliases, timeouts).leezy.pheanstalk (default connection) and leezy.pheanstalk.<connection_name>.pda/pheanstalk README — the bundle is a thin wrapper, so most day-to-day API knowledge comes from there.First use case — put and reserve a job:
use Pheanstalk\PheanstalkInterface;
public function dispatch(PheanstalkInterface $pheanstalk): void
{
$pheanstalk
->useTube('emails')
->put(json_encode(['to' => '[email protected]', 'subject' => 'Hi']), PheanstalkInterface::DEFAULT_PRIORITY, 0, 60);
$job = $pheanstalk->watch('emails')->reserve(5); // 5s timeout
if ($job) {
// handle payload
$pheanstalk->delete($job);
}
}
Start beanstalkd locally (beanstalkd -p 11300) so you can exercise the flow before wiring workers.
Inject the connection service, not the bundle. Constructor-inject PheanstalkInterface (or the concrete Pheanstalk) and keep queue code in a dedicated producer/consumer class. Reserve the Pheanstalk client for one class per tube to keep lifecycle calls obvious.
Producer pattern — always set tube, priority, delay, TTR explicitly:
$pheanstalk->useTube('reports')->put(
$payload,
PheanstalkInterface::DEFAULT_PRIORITY, // lower = higher priority
$delaySeconds, // 0 = ready now
$ttrSeconds // time reserved job may run before reappearing
);
Consumer/worker pattern: a long-running console command that loops reserve(), dispatches to a handler, then calls exactly one of delete() (success), release() (retry), or bury() (dead-letter). Handle signals (SIGTERM/SIGINT) so supervisor can restart workers cleanly.
while (!$this->shouldStop) {
$job = $pheanstalk->watch('reports')->ignore('default')->reserve(5);
if (!$job) { continue; }
try {
($this->handler)($job->getData());
$pheanstalk->delete($job);
} catch (\Throwable $e) {
$pheanstalk->release($job, PheanstalkInterface::DEFAULT_PRIORITY, 10); // retry in 10s
}
}
Laravel bridge pattern: register a singleton in AppServiceProvider and alias the interface:
$this->app->singleton(PheanstalkInterface::class, fn () => new \Pheanstalk\Pheanstalk('127.0.0.1', 11300));
Then inject PheanstalkInterface into jobs/controllers. Mirror the bundle's per-connection idea by creating named bindings (pheanstalk.reports, pheanstalk.emails) if you use multiple tubes/servers.
Payload convention: serialize a small envelope ({class, method, args, attempts}) rather than raw arrays so handlers stay versionable; store attempts in the payload and enforce max retries in the worker before burying. The bundle does not serialize for you.
Multiple connections: define several entries under connections (e.g. default, priority) and inject leezy.pheanstalk.priority where needed. Use separate tubes and, ideally, separate beanstalkd instances for workloads you do not want competing for memory.
Monitoring/stats: call $pheanstalk->stats(), statsTube('emails'), and peekReady('emails') from a health command or debug endpoint. For Symfony, inspect available bundle commands with php bin/console list | grep -i leezy rather than assuming command names.
Testing: the client talks to a real beanstalkd over TCP, so unit tests should mock PheanstalkInterface (it is injectable) and integration tests should point at a disposable beanstalkd container. There is no in-memory fake bundled.
pda/pheanstalk and Symfony versions, and be prepared to fork or replace it with direct pda/pheanstalk usage. Check PHP/Symfony constraint compatibility before upgrading either.Queue::push(), dispatch(), or Horizon. If you need Laravel-native queues, use the built-in Redis/database/SQS drivers; use beanstalkd only if you deliberately want its semantics.Pheanstalk\Pheanstalk, Pheanstalk\Job, Pheanstalk\PheanstalkInterface); older tutorials use the global Pheanstalk class. The bundle's service definitions are tied to whichever major version it requires — verify composer show pda/pheanstalk and match imports to it.reserve() blocks. reserve(0) blocks forever; always pass a timeout in workers so shutdown signals are observed. Catch TimeoutException and continue rather than letting it crash the worker.delete(), the job reappears after its TTR. Keep TTR comfortably above expected handler runtime; a too-short TTR causes duplicate processing.0 is highest. Copying priority constants from another queue system will produce surprising ordering.default is watched automatically, so call ignore('default') when you only want your tube.release() for retries, bury() for poison messages. release() with a delay is your backoff mechanism; burying keeps failed jobs inspectable via peekBuried() instead of silently dropping them.host:port entries; a malformed string or unreachable host throws ConnectionException at first use, not at container boot. Warm the connection in a health check if you want early failure.-b); a restart clears the queue. Do not treat it as a system of record.bin/console cache:clear after changing leezy_pheanstalk config; stale container definitions are a common "why is it still connecting to the old host" cause.leezy.pheanstalk.*), decorating the Pheanstalk service, or bypassing the bundle entirely and constructing the client in your own factory. The client itself is extensible via Pheanstalk's command API.telnet 127.0.0.1 11300 and issue stats, stats-tube emails, peek-ready emails, and list-tubes to see exactly what the client sees; most "lost job" reports are TTR/release mistakes rather than bundle bugs.How can I help you explore Laravel packages today?