hasel/aphpsurd-bundle

Symfony bundle for Absurd, a Postgres-native durable execution engine

Maintainers

Package info

github.com/hasel-at/aphpsurd-bundle

Type:symfony-bundle

pkg:composer/hasel/aphpsurd-bundle

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-04-19 17:12 UTC

This package is auto-updated.

Last update: 2026-04-19 17:49:17 UTC


README

aphpsurd bundle

hasel/aphpsurd-bundle

Symfony bundle wrapping ruudk/absurd-php-sdk, the PHP client for Absurd, a Postgres-native durable execution engine built by earendil-works.

Installation

composer require hasel/aphpsurd-bundle

Symfony Flex registers the bundle automatically. Without Flex, add it manually:

// config/bundles.php
return [
    Hasel\AphpsurdBundle\AphpsurdBundle::class => ['all' => true],
];

Configuration

The bundle picks up the default Doctrine DBAL connection automatically. Everything else is opt-in.

# config/packages/aphpsurd.yaml
aphpsurd:
    doctrine: true             # true = default connection, string = named connection, false = use dsn
    dsn: '%env(ABSURD_DSN)%'   # only when doctrine: false
    default_queue: default
    claim_timeout: 120         # seconds
    poll_interval: 250         # milliseconds
    default_max_attempts: 5
    queues:
        notifications:
            max_attempts: 3
            rate_limiter: notifications  # optional, requires symfony/rate-limiter
            retry_strategy:
                kind: exponential  # exponential | linear | fixed | none
                base_seconds: 2
                factor: 2.0
                max_seconds: 60
            cancellation:
                max_duration: 300
                max_delay: 30

Per-queue settings are applied as defaults when spawning tasks on that queue. Per-call SpawnOptions always take precedence.

Tasks

Tasks are invokable classes tagged with #[AsAbsurdTask], auto-discovered via Symfony's autoconfiguration.

use Hasel\AphpsurdBundle\Attribute\AsAbsurdTask;
use Ruudk\Absurd\Task\Context;

#[AsAbsurdTask('send-invitations', queue: 'notifications')]
final class SendInvitationsTask
{
    public function __invoke(SendInvitationsPayload $params, Context $ctx): mixed
    {
        // ...
    }
}

Typed payload deserialization requires symfony/serializer.

Spawning tasks

Inject AbsurdClientInterface wherever you need to produce work — controllers, services, event listeners. It resolves task class names to their configured task name and queue automatically, applies per-queue defaults from config, and in debug mode integrates with the Symfony profiler.

use Hasel\AphpsurdBundle\AbsurdClientInterface;

final class OrderController
{
    public function __construct(private AbsurdClientInterface $absurd) {}

    public function checkout(): Response
    {
        $this->absurd->spawn(SendInvitationsTask::class, $payload);
        $this->absurd->emitEvent('order.confirmed', ['orderId' => $id]);
        // ...
    }
}

Override defaults per call:

use Ruudk\Absurd\Task\SpawnOptions;

$this->absurd->spawn(SendInvitationsTask::class, $payload, new SpawnOptions(maxAttempts: 1));

Workers

Run one worker process per queue. Graceful shutdown on SIGTERM/SIGINT requires ext-pcntl.

php bin/console absurd:consume
php bin/console absurd:consume --queue=notifications
php bin/console absurd:consume --limit=100 --time-limit=3600 --memory-limit=128M

Rate limiting

Per-queue rate limiting is supported via symfony/rate-limiter. Install it first:

composer require symfony/rate-limiter

Configure a rate limiter in Symfony (e.g. using the framework bundle), then reference it by service ID in the queue config:

# config/packages/framework.yaml
framework:
    rate_limiter:
        notifications:
            policy: fixed_window
            limit: 10
            interval: '1 minute'
            storage_service: cache.app
# config/packages/aphpsurd.yaml
aphpsurd:
    queues:
        notifications:
            rate_limiter: notifications

After each batch of tasks the worker consumes one token per task processed. If the limit is exhausted it waits until the window resets before continuing. For multi-host deployments use a shared storage backend (e.g. Redis) for the limiter.

Service reset between tasks

The bundle ships a ResetServicesListener that calls Symfony's services_resetter after every completed or failed task. This resets Doctrine entity managers, Monolog buffers, and anything else tagged kernel.reset.

Treat the worker like any long-running process: don't cache EntityManagerInterface results in properties, fetch them per-task.

Migration

The bundle vendors the Absurd SQL schema. The recommended approach is to keep the Absurd schema in your Doctrine migration history alongside the rest of your database.

To prevent Doctrine from picking up the absurd schema in its own migrations, add a schema filter to your DBAL config:

# config/packages/doctrine.yaml
doctrine:
    dbal:
        schema_filter: '~^(?!absurd)~'

It is known, that Doctrine might still create a CREATE SCHEMA absurd statement inside the down-step in an otherwise empty migration.

When you run make:migration, the bundle checks whether your installed Absurd schema is behind the version the bundle requires. If doctrine/doctrine-migrations-bundle is installed, it automatically generates a dedicated migration file with the pending SQL. Otherwise it prints the pending filenames for you to apply manually.

php bin/console make:migration
# with doctrine/migrations:    → generates DoctrineMigrations/VersionXXX.php
# without doctrine/migrations: → warning listing pending SQL files to apply manually

For non-Doctrine setups, apply the schema directly:

php bin/console absurd:migrate
php bin/console absurd:migrate --dry-run  # list pending files without applying

Commands

Command Description
absurd:migrate Apply Absurd schema migrations
absurd:migrate --dry-run List pending migration files without applying them
absurd:setup-queues Create configured queues in the database
absurd:setup-queues --prune Also remove queues not in config
absurd:consume Start a worker
absurd:cleanup Delete old completed tasks and events
absurd:list-tasks List all registered task handlers

Profiler

In debug mode the bundle registers a Web Debug Toolbar panel showing all spawned tasks (payload, options, errors) and emitted events per request.

AI

In the spirit of the original absurd code base, this bundle was written with support from Claude Code.