glit/resque-bundle
glit/resque-bundle is a Symfony bundle for integrating Resque, the Redis-backed PHP job queue. It helps wire Resque queues and workers into Symfony so applications can dispatch and process background jobs asynchronously.
First, check the constraints before you write a line of code. Open composer.json in the repo. This bundle targets Symfony2 (app/console, AppKernel, symfony/symfony ~2.x, PHP 5.x). If you are on Laravel — or Symfony 4+ — you cannot install this. Treat the repo as a reference implementation of a php-resque integration and read DependencyInjection/Configuration.php, Command/, and Resources/config/services.xml for the wiring pattern. The conceptual map for a Laravel developer is direct:
| This bundle | Laravel equivalent |
|---|---|
Resque::enqueue($queue, $class, $args) |
Queue::push(new Job($args)) |
glit:resque:worker:start |
php artisan queue:work |
Redis resque:failed list |
failed_jobs table |
| php-resque-scheduler | Queue::later() |
If you are actually on Symfony2, the minimal path:
composer require glit/resque-bundle
# AppKernel.php
new Glit\ResqueBundle\GlitResqueBundle(),
# config.yml
glit_resque:
redis:
host: 127.0.0.1
port: 6379
database: 0 # give this app its own DB — see Gotchas
Write the smallest possible job — a plain class with a perform() method; php-resque instantiates it by string name, so it must be autoloadable and have a no-arg constructor:
class SendWelcomeEmailJob
{
public $args; // php-resque injects this
public function perform()
{
$userId = $this->args['user_id'];
// do the slow thing
}
}
Enqueue from a controller or service, then run a worker:
php app/console glit:resque:worker:start default --interval=5
First use case: take one slow, non-user-facing side effect (welcome email, thumbnail generation, third-party sync) out of the request cycle. Don't start with retries, scheduling, or batches — get one job round-tripping through Redis and a supervised worker first.
Keep the job class a dumb adapter. This is the single most important pattern. The job's perform() should resolve a service and delegate:
class SyncOrderJob
{
public $args;
public function perform()
{
$container = /* whatever hook the bundle gives you */;
$container->get('app.order_sync')->sync($this->args['order_id']);
}
}
Business logic lives in AppBundle\Service\OrderSync, which is unit-testable without Redis and portable to Laravel's Queue::push later. If you write logic inside perform(), you have coupled your domain to a 2013-era fork model.
Payloads must be JSON scalars. Only strings, ints, floats, bools, arrays, nulls. No entities, no DateTime objects, no binary. Pass IDs and re-fetch inside the job:
Resque::enqueue('mail', 'SendWelcomeEmailJob', ['user_id' => $user->getId()]);
Passing serialized entities is the classic mistake — the entity is stale by the time the worker picks it up, and Doctrine will hand you a detached object.
Split queues by latency class, not by feature. default for anything user-triggered, mail for bulk sends, exports for long-running work that can starve everything else. Then give each queue its own supervised worker group so an export can't block a password reset email.
Supervise with Supervisor, one program per queue:
[program:resque-default]
command=php /var/www/app/console glit:resque:worker:start default --interval=5
numprocs=2
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
stopsignal=QUIT
stdout_logfile=/var/log/resque-default.log
stopsignal=QUIT matters: php-resque handles SIGQUIT gracefully (finish current job, then exit) while SIGTERM kills mid-job. Using SIGTERM guarantees you will eventually see a job that is half-done and gets retried by nobody.
Design for idempotency from day one. php-resque has no retry/backoff and no --tries; a failed job lands in the Redis failed list and stays there. That does not mean jobs run once — a worker killed mid-perform() leaves the job in the queue's "in-progress" state and it will be reprocessed. Every perform() should be safe to run twice: check-then-act, or use a lock key in Redis.
Dispatch through a thin interface, not Resque::enqueue scattered everywhere. One JobDispatcher service with dispatch($queue, $class, $args) means you can swap the backend, log every enqueue, and fake the dispatcher in tests:
$dispatcher->dispatch('mail', SendWelcomeEmailJob::class, ['user_id' => 42]);
Migration tip: because payloads are JSON scalars and logic lives in services, moving to Laravel later is mechanical — dispatch() becomes Queue::push(), and the job classes get a handle() that calls the same service.
Doctrine connections die inside long-running workers. The worker process boots the kernel once and keeps running for days. MySQL drops idle connections after wait_timeout (default 8h), and the next job throws "MySQL server has gone away." Mitigate with a short --interval (keeps the connection warm) and explicit cleanup at the end of perform():
$em = $container->get('doctrine')->getManager();
$em->clear(); // kill the identity map — it will grow forever otherwise
$em->getConnection()->close(); // forces a clean reconnect next job
The identity-map leak is the sneakier bug: memory climbs steadily and you blame PHP, when it's a thousand hydrated entities nobody released.
Redis key collisions. php-resque writes unprefixed keys: resque:queue:default, resque:workers, resque:stat:processed. Two apps on the same Redis database will steal each other's jobs and corrupt each other's worker registry. Always isolate by database index or a dedicated Redis instance. There is no configurable namespace.
Job class names are strings that live in Redis. Rename or delete SendWelcomeEmailJob while jobs are queued and the worker throws CouldNotLoadJob. Treat job class names as a public API: deprecate rather than rename, and keep a stub class that delegates to the new name.
The backend is a static global. Resque::setBackend() is process-wide. The bundle sets it during kernel boot, so enqueueing from inside Symfony works — but any standalone script, cron entry, or test that calls Resque::enqueue outside the kernel must call setBackend() itself or jobs silently go to localhost:6379/0.
Stale workers wedge your monitoring. Workers register themselves in the resque:workers set and clean up on graceful exit. kill -9 (or an OOM kill) leaves a ghost entry forever, and anything that reads worker counts will lie to you. Check the set directly when debugging:
redis-cli smembers resque:workers
redis-cli llen resque:queue:default
redis-cli lrange resque:failed 0 10
Failures are invisible by default. Nothing retries, nothing alerts. If you keep this stack, add a small cron that polls resque:failed, logs the count to your metrics, and replays selected jobs. Otherwise a broken job silently accumulates for weeks.
No container injection by default. php-resque does new $class, so your job gets no constructor arguments. Look in the bundle's Resources/config/ for how it hooks job instantiation; the usual extension point is to implement ContainerAwareInterface on the job and let the bundle's job factory set the container, or to define your own abstract base job class that pulls services from a statically-stashed container. Verify which one this version supports before designing around it — this is where bundle forks diverge most.
Debug a single job synchronously by instantiating it and setting $args by hand. Do not debug through Redis; you will waste hours on serialization when the bug is in your service.
Windows is a non-starter. php-resque forks via pcntl_fork. Linux/macOS only.
How can I help you explore Laravel packages today?