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

Doctrine Delay Queue Driver Bundle Laravel Package

mcfedr/doctrine-delay-queue-driver-bundle

Symfony bundle that integrates a Doctrine-backed delay queue driver, enabling applications to schedule and process delayed jobs through Doctrine with straightforward bundle configuration for queue-based workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations. This is a Symfony bundle for Doctrine, not a Laravel package. There is no Laravel service provider, no package auto-discovery, no config/queue.php driver, and no Artisan integration. With 2 stars, zero dependents, and a last release in April 2018, treat it as either:

  1. A drop-in for a legacy Symfony 3/4 app using Doctrine DBAL, or
  2. A reference implementation you borrow from when building a Doctrine-backed delayed-job table in Laravel.

For a Laravel codebase, the realistic first step is to inspect the repository, not install it blindly:

  • src/ — find the driver class and the job/task contract.
  • DependencyInjection/Configuration.php — find the actual config keys and defaults.
  • Resources/config/ — find service IDs and how the driver is constructed.
  • Command/ — find the worker command and its options (--sleep, --limit, --queue, etc.).
  • The bundle’s schema definition or migration — copy the exact job table columns.

Minimal adoption path in a Symfony app:

composer require mcfedr/doctrine-delay-queue-driver-bundle

Enable the bundle in AppKernel.php (Symfony 3/4) or config/bundles.php, configure the Doctrine connection, create the queue table via Doctrine schema tool or a migration, enqueue a job, and run the bundle’s worker command under Supervisor.

Minimal adoption path in Laravel:

composer require mcfedr/doctrine-delay-queue-driver-bundle

Then hand-wire it:

  1. Create a migration matching the bundle’s expected queue table exactly.
  2. Bind the driver in a service provider using Laravel’s Doctrine DBAL connection, or a dedicated connection.
  3. Write an Artisan command that reserves and processes jobs in a loop.
  4. Register that command in app/Console/Kernel.php or schedule it.

First useful use case: transactional delayed work. Inside the same database transaction as a domain change, insert a job row with available_at = now + delay. A worker polls the table and executes the job later. That transactional coupling is the only real reason to choose a Doctrine-backed queue over Laravel’s native database queue.

For most Laravel teams, the lower-risk equivalent is dispatch($job)->delay($seconds) with the built-in database driver, or a queued job with $this->release($delay). Use this package only when you specifically need the queue row in the same Doctrine connection/transaction as your domain writes.

Implementation Patterns

Pattern 1: Transactional enqueue (the main reason to use this)

Insert the job in the same transaction as the business write:

$em->transactional(function ($em) use ($driver) {
    $order = new Order(/* ... */);
    $em->persist($order);
    $em->flush();

    $driver->enqueue(new SendOrderConfirmation($order->getId()), delay: 300);
});

If the transaction rolls back, the job disappears with it. This is the outbox pattern in miniature and is genuinely useful when you cannot tolerate “order saved but email job queued” or vice versa.

In Laravel with Doctrine DBAL:

DB::transaction(function () use ($driver, $order) {
    $order->save(); // or Doctrine flush
    $driver->enqueue(new SendOrderConfirmation($order->id), 300);
});

Pattern 2: Worker command with controlled polling

Wrap the package’s worker logic in an Artisan command:

class WorkDelayQueue extends Command
{
    protected $signature = 'delay-queue:work {--queue=default} {--sleep=3} {--limit=100}';

    public function handle(DelayQueueDriver $driver): int
    {
        while (true) {
            $jobs = $driver->reserve($this->option('queue'), $this->option('limit'));

            foreach ($jobs as $job) {
                try {
                    $this->dispatchJob($job); // resolve handler, execute
                    $driver->acknowledge($job);
                } catch (Throwable $e) {
                    $driver->fail($job, $e);
                }
            }

            sleep((int) $this->option('sleep'));
        }
    }
}

Run it with Supervisor or systemd. Do not rely on schedule:run for a long-lived worker unless you use withoutOverlapping() and a short runInBackground() loop.

Pattern 3: Laravel service provider bridge

Do not register the Symfony bundle. Bind only the driver:

public function register(): void
{
    $this->app->singleton(DelayQueueDriver::class, function ($app) {
        $connection = $app['db']->connection()->getDoctrineConnection();

        return new DelayQueueDriver($connection, [
            'table' => 'delay_queue_jobs',
        ]);
    });
}

Read the bundle’s service definition to get the real constructor arguments. Do not guess them from the README; the README is from 2018.

Pattern 4: Queue separation and prioritization

Use a queue column and run separate workers:

php artisan delay-queue:work --queue=emails
php artisan delay-queue:work --queue=reports --sleep=10

This is the simplest way to prevent slow report jobs from blocking transactional emails.

Pattern 5: Retry and backoff

If the driver does not ship retry semantics, implement them around it:

  • Add attempts, last_error, failed_at, and reserved_at columns if missing.
  • On failure, increment attempts and re-enqueue with exponential backoff.
  • After N attempts, move to a failed_jobs-style table so you can inspect and replay.

Pattern 6: Integration with Laravel events and logging

Wrap execution in Laravel events (JobProcessing, JobProcessed, JobFailed) or your own events. This gives you logging, metrics, and alerting without modifying the queue driver.

Gotchas and Tips

The package is old and small. Last release 2018, 2 stars, no dependents. Expect PHP 7.1-era code, Symfony 3/4 service definitions, and Doctrine DBAL 2.x assumptions. Before adopting, run:

composer why-not mcfedr/doctrine-delay-queue-driver-bundle

on your Laravel 10/11 or Symfony 6/7 project. Dependency conflicts are likely. If Composer refuses, do not fight it; copy the driver logic instead.

Symfony bundle ≠ Laravel package. You cannot add it to config/app.php providers. The Bundle class, extension alias, YAML config, and console command are Symfony-specific. In Laravel you must instantiate the driver manually and write your own worker command.

Schema drift is the most common failure. The driver expects exact column names and types. Read the bundle’s schema definition and mirror it in a Laravel migration. Pay attention to datetime vs timestamp, nullable reserved_at, and index names. Do not let Doctrine schema tool and Laravel migrations both manage the table.

Concurrency safety is not guaranteed by age. Check whether reservation uses SELECT ... FOR UPDATE, SKIP LOCKED, or an atomic UPDATE ... WHERE reserved_at IS NULL. If it does not, multiple workers will double-process jobs. On PostgreSQL or MySQL 8, add SKIP LOCKED; on older MySQL, use a single worker or a reservation update with a unique token.

Delay precision is polling precision. A 300-second delay means “available after 300 seconds and picked up on the next poll.” If your worker sleeps 10 seconds, expect up to 10 seconds of jitter. Do not use this for precise scheduling.

Store UTC everywhere. The database, PHP, and worker must agree on timezone. Mixing Carbon::now() with a database NOW() in a different timezone causes jobs to run immediately or hours late.

Payload serialization is a compatibility trap. If the package uses PHP serialize(), renaming a job class or changing constructor arguments breaks existing rows. Prefer JSON payloads with explicit handler resolution if you extend the driver. Never unserialize payloads from untrusted sources.

Retries, dead-letter queues, and monitoring are probably not included. Build them yourself: attempts, backoff, failed table, queue-depth query, alert on the oldest available_at. The bundle is a driver, not a full queue platform.

Testing against SQLite will mislead you. SQLite lacks SKIP LOCKED and has different locking behavior. Run concurrency tests against the same database engine you use in production.

Extension points to look for: a driver interface you can reimplement, a job/task contract, the worker command options, and Symfony service tags. In Laravel, the cleanest extension point is your own service provider plus Artisan command; keep the third-party surface tiny.

Security: job payloads often contain IDs and sometimes PII. Restrict database access, avoid storing secrets in payloads, and treat queued rows as sensitive data.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin