PHPackages                             daycry/jobs - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. daycry/jobs

ActiveLibrary[Queues &amp; Workers](/categories/queues)

daycry/jobs
===========

Codeigniter 4 Jobs - scheduled &amp; queues

v3.0.0(1mo ago)01.7k↑20%1MITPHPPHP ^8.2CI passing

Since Oct 6Pushed 1mo agoCompare

[ Source](https://github.com/daycry/jobs)[ Packagist](https://packagist.org/packages/daycry/jobs)[ Docs](https://github.com/daycry/jobs)[ RSS](/packages/daycry-jobs/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (10)Dependencies (30)Versions (23)Used By (1)

[![Donate](https://camo.githubusercontent.com/604e3db9c8751116b3f765aad0353ec7ded655bbe8aaacbc38d8c4a6b784b3ed/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f6e6174652d50617950616c2d677265656e2e737667)](https://www.paypal.com/donate?business=SYC5XDT23UZ5G&no_recurring=0&item_name=Thank+you%21&currency_code=EUR)

---

🚀 **[VIEW ONLINE DOCUMENTATION](https://daycry.github.io/jobs/)** 🚀

---

Codeigniter Jobs
================

[](#codeigniter-jobs)

[![Docs](https://github.com/daycry/jobs/actions/workflows/docs.yml/badge.svg)](https://daycry.github.io/jobs/)[![Tests](https://github.com/daycry/jobs/actions/workflows/php.yml/badge.svg?branch=master)](https://github.com/daycry/jobs/actions/workflows/php.yml)[![Static Analysis](https://github.com/daycry/jobs/actions/workflows/analyze.yml/badge.svg?branch=master)](https://github.com/daycry/jobs/actions/workflows/analyze.yml)[![Code Style](https://github.com/daycry/jobs/actions/workflows/cs.yml/badge.svg?branch=master)](https://github.com/daycry/jobs/actions/workflows/cs.yml)[![Composer](https://github.com/daycry/jobs/actions/workflows/composer.yml/badge.svg?branch=master)](https://github.com/daycry/jobs/actions/workflows/composer.yml)[![Security](https://github.com/daycry/jobs/actions/workflows/security.yml/badge.svg?branch=master)](https://github.com/daycry/jobs/actions/workflows/security.yml)[![Coverage status](https://camo.githubusercontent.com/2bb2d058589501b0471388b175f314e1cf2010dfb1d2cda64303360d9b6330fa/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f6461796372792f6a6f62732f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/daycry/jobs?branch=master)[![Downloads](https://camo.githubusercontent.com/3c5e80da9d500a308a3898b5e6a04b6156d7efef1cdb189ef93e2084702b79d5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6461796372792f6a6f6273)](https://packagist.org/packages/daycry/jobs)[![Monthly Downloads](https://camo.githubusercontent.com/adabfc58e95cb4bfc6b0e88e500b38a1354488b349ca25a26bf9b1669f4affad/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f6461796372792f6a6f6273)](https://packagist.org/packages/daycry/jobs)[![GitHub release (latest by date)](https://camo.githubusercontent.com/1811f19816f8ce3edaeb2f375641c00da54da6fff9eacfc3c30f56e731a92520/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f6461796372792f6a6f6273)](https://packagist.org/packages/daycry/jobs)[![GitHub stars](https://camo.githubusercontent.com/92f46b98516ef5123b8c2cc96fc556162dfc25ca044f6c5158ab2119313017c6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f6461796372792f6a6f6273)](https://packagist.org/packages/daycry/jobs)[![GitHub license](https://camo.githubusercontent.com/20b2bba64571ea07e548719a5645821905984ce377039547f7ae59acfc2f271c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6461796372792f6a6f6273)](https://github.com/daycry/jobs/blob/master/LICENSE)

Job scheduling and queue processing for CodeIgniter 4. Define work with a fluent, immutable builder, dispatch it to one of several queue backends (Sync, Database, Redis, Beanstalk, Azure Service Bus), and process it with a resilient worker featuring real timeouts, retries with backoff, signed envelopes, idempotency and per-queue handler allowlists.

> **v3.0** is a complete, single-architecture rewrite. The legacy mutable `Job` builder, the V1 `Scheduler`, the performance loggers and the `QueueManager` have been removed. See the [Migration v1 → v3](docs/MIGRATION-v1-to-v3.md) guide if you are upgrading.

[Installation](#installation) · [Quick Start](#quick-start) · [Handlers](#handlers) · [Backends](#queue-backends) · [Commands](#cli-commands) · [Cron](#scheduling-cron) · [Security](#security)

[Configuration](docs/CONFIGURATION.md) · [Queues](docs/QUEUES.md) · [CLI Commands](docs/COMMANDS.md) · [Retries](docs/RETRIES.md) · [Architecture](docs/ARCHITECTURE.md)

---

Features
--------

[](#features)

- **Fluent definition API**: `Jobs::define()` returns a throwaway `JobBuilder` that accumulates configuration and materialises an immutable `JobDefinition` on `dispatch()` / `toDefinition()`.
- **Five built-in handlers**: `command`, `shell`, `closure`, `event`, `url` — each receiving an immutable `JobContext`, never the builder.
- **Five queue backends** behind one `QueueBackend` contract: `sync`, `database`, `redis`, `beanstalk`, `serviceBus`.
- **Cron scheduler**: register recurring jobs in `Config\Jobs::init()`; due jobs are run inline or enqueued, in topological dependency order.
- **Resilient worker**: a single attempt per fetch, retries via the backend with backoff, real (interrupting) timeouts, circuit breaker, per-queue rate limits and a dead-letter queue.
- **Secure by default**: HMAC-SHA256 envelope signing, per-queue handler allowlists, deny-by-default `ShellHandler`, event allowlist and SSRF-hardened `UrlHandler`.
- **At-least-once delivery** on every persistent backend, recovered by `reapExpired()`; opt-in idempotency keeps re-deliveries safe.

Installation
------------

[](#installation)

```
composer require daycry/jobs
```

Run the package migrations (queue table, lease columns, history table):

```
php spark migrate -n "Daycry\Jobs"
```

Optionally publish the config so you can customise it under `app/Config/Jobs.php`:

```
php spark jobs:publish
```

Backend-specific extensions are optional and only needed for the backend you use:

- `ext-redis` for the `redis` backend.
- `pda/pheanstalk` for the `beanstalk` backend.
- `ext-pcntl` (Unix) for real, interrupting timeouts and graceful worker shutdown.

Requires PHP `^8.2` and CodeIgniter 4. See [Installation](docs/installation.md) for details.

Quick Start
-----------

[](#quick-start)

Define and dispatch a job with the fluent facade:

```
use Daycry\Jobs\Jobs;

// Enqueue a spark command to run daily at 02:00 on the "reports" queue, with 3 retries.
$id = Jobs::define('command', 'app:report')
    ->named('daily-report')
    ->dailyAt('02:00')
    ->queue('reports')
    ->maxRetries(3)
    ->timeout(120)
    ->dispatch();
```

`dispatch()` enqueues the definition onto the configured default backend (`Config\Jobs::$worker`) and returns the backend-assigned id. Pass a name to target a specific backend, e.g. `->dispatch('redis')`.

To build a definition without enqueuing it (e.g. for tests), use `toDefinition()`:

```
$definition = Jobs::define('closure', static fn (): string => 'done')->toDefinition();
```

Resolve a backend directly when you need lower-level access:

```
$backend = Jobs::backend('database'); // or Jobs::backend() for the default worker
```

### Builder API

[](#builder-api)

`Jobs::define(string $handler, mixed $payload = null): JobBuilder` opens the builder. Chain any of:

MethodPurpose`named(string)`Friendly name for logs/metrics.`queue(?string)`Target queue; `null` lets the runner pick the first configured queue.`priority(int)`Higher = sooner (backend-dependent). Default `5`.`maxRetries(?int)`Retries after the first attempt. Total runs = `maxRetries + 1`.`timeout(?int)`Per-attempt timeout in seconds (falls back to `defaultTimeout`).`scheduledAt(DateTimeImmutable)`Earliest run time.`singleInstance(bool)`Prevent concurrent runs of the same job.`environments(...)`Restrict to specific CI4 environments.`dependsOn(...)`Names that must succeed first (scheduler only).`idempotencyKey(?string)`Opt-in deduplication key.`enabled(bool)` / `disable()`Toggle a definition on/off.Scheduling helpers: `cron(string)`, `everyMinute(?int)`, `everyXMinutes(int)`, `hourly()`, `hourlyAt(int)`, `daily()`, `dailyAt('HH:MM')`, `weekly()`, `monthly()`, `quarterly()`, `yearly()`.

Terminators: `toDefinition(): JobDefinition` and `dispatch(?string $backend = null): string`.

Handlers
--------

[](#handlers)

A handler contains business logic only. It implements `JobHandlerInterface` and receives an immutable `JobContext` ( `payload`, `name`, `queue`, `attempt`, `meta` ):

```
use Daycry\Jobs\Handlers\JobHandlerInterface;
use Daycry\Jobs\Execution\JobContext;

interface JobHandlerInterface
{
    public function handle(JobContext $ctx): mixed;
    public function beforeRun(JobContext $ctx): void;          // optional lifecycle hook
    public function afterRun(JobContext $ctx, $result): void;  // optional lifecycle hook
}
```

Extend `AbstractJobHandler` to get no-op `beforeRun()` / `afterRun()` and implement only `handle()`. For typed payloads, extend `TypedJobHandler`: declare `payloadType(): string` and implement `run(object $dto)`; the base class rehydrates the DTO from the `JobContext` payload (array, object or JSON string).

```
use Daycry\Jobs\Handlers\TypedJobHandler;

final class ProcessImport extends TypedJobHandler
{
    public function payloadType(): string { return ImportRequest::class; }

    protected function run(object $payload): mixed
    {
        // $payload is an ImportRequest instance hydrated from the queue message.
        return 'imported';
    }
}
```

### Built-in handler keys

[](#built-in-handler-keys)

Configured in `Config\Jobs::$handlers`:

KeyClassPayload`command``CommandHandler`Non-empty spark command string, e.g. `'app:report'`.`shell``ShellHandler`A string or `argv` array; executed via `proc_open` (no shell).`closure``ClosureHandler`A callable; **sync backend only** (closures do not survive serialization).`event``EventHandler``['name' => string, 'data' => array]`.`url``UrlHandler``['method' => string, 'url' => string, 'options' => array]`.```
Jobs::define('url', ['method' => 'GET', 'url' => 'https://example.com'])->queue('web')->dispatch();
Jobs::define('event', ['name' => 'user.registered', 'data' => ['user_id' => 123]])->dispatch();
```

Queue Backends
--------------

[](#queue-backends)

All backends implement a single `QueueBackend` contract with lease semantics: `enqueue`, `fetch` (returns a lease), `ack`, `nack(?delay)`, `abandon`, `reapExpired`. Delivery is **at-least-once** on every persistent backend, so handlers should be idempotent.

NameClassNotes`sync``SyncBackend`Runs inline at `enqueue()` time. Default. No worker needed.`database``DatabaseBackend`Persistent; `FOR UPDATE SKIP LOCKED`; reaped by `jobs:queue:reap`.`redis``RedisBackend`Reliable queue (RPOPLPUSH); reaped by `jobs:queue:reap`.`beanstalk``BeanstalkBackend`Native delayed retry; recovers leases natively.`serviceBus``ServiceBusBackend`Azure Service Bus peek-lock; recovers leases natively.Backends are configured in `Config\Jobs::$backends`; the active default is `Config\Jobs::$worker`. See [Queues](docs/QUEUES.md).

CLI Commands
------------

[](#cli-commands)

```
# Start a worker for a queue (graceful SIGTERM/SIGINT, circuit breaker, rate limits)
php spark jobs:queue:work reports
php spark jobs:queue:work reports --once            # process a single cycle and exit
php spark jobs:queue:work reports --max 100         # process at most N cycles
php spark jobs:queue:work reports --backend redis   # override the configured backend

# Reclaim leases from crashed/stalled workers (Database / Redis).
# Beanstalk and Service Bus recover leases natively.
php spark jobs:queue:reap reports
php spark jobs:queue:reap reports --backend redis

# Run due scheduled jobs — wire this to your OS cron, every minute.
php spark jobs:cronjob:run

# Maintenance: purge completed/failed rows from the database backend.
php spark jobs:queue:purge --status completed
```

See [CLI Commands](docs/COMMANDS.md) for every option.

Scheduling (Cron)
-----------------

[](#scheduling-cron)

Register recurring jobs in `Config\Jobs::init()`. Each `define()` returns the same fluent builder. Jobs that declare a `queue()` are **enqueued**; the rest run **inline**. `enabled()` / `environments()` are honoured, and `dependsOn()` produces a topological execution order.

```
// app/Config/Jobs.php
use Daycry\Jobs\Cron\Scheduler;

public function init(Scheduler $scheduler): void
{
    $scheduler->define('command', 'app:report')
        ->named('reports')
        ->dailyAt('03:00')
        ->queue('reports');                 // enqueued to the "reports" queue

    $scheduler->define('shell', 'ls')
        ->named('shell_test')
        ->everyMinute()
        ->singleInstance();                 // runs inline (no queue)

    $scheduler->define('command', 'app:send-report')
        ->named('send-reports')
        ->dailyAt('03:30')
        ->dependsOn('reports');             // runs after "reports"
}
```

Run the scheduler from your system cron every minute:

```
* * * * * cd /path/to/app && php spark jobs:cronjob:run >> /dev/null 2>&1
```

Security
--------

[](#security)

- **Envelope signing**: queue envelopes are signed with HMAC-SHA256 over immutable identity fields at enqueue time. The key resolves from `Config\Jobs::$signingKey` → `env('JOBS_SIGNING_KEY')` → the CI4 Encryption key. When `$verifyEnvelopeSignature` is `true`, the worker **rejects** tampered or forged messages.
- **Per-queue handler allowlist**: `Config\Jobs::$queueHandlers` restricts which handler keys a queue may run, e.g. `['reports' => ['command'], 'web' => ['url', 'event']]`. Set this in production so remote queues cannot reach `shell` / `command`.
- **ShellHandler is deny-by-default**: an empty `$allowedShellCommands` rejects execution. Populate it with allowed binaries (matched by `realpath`), or opt out explicitly with `$allowAllShellCommands = true`. Commands run via `proc_open` with an `argv` array — never `/bin/sh -c`.
- **EventHandler allowlist**: only events listed in `$allowedEvents` may fire (empty = deny all).
- **UrlHandler anti-SSRF**: http/https only, private/reserved IPv4 + IPv6 targets rejected, SSL verification forced on, redirects disabled.

Resilience
----------

[](#resilience)

- **Retries**: `maxRetries` runs the job at most `maxRetries + 1` times. On failure with retries left the worker `nack`s with a backoff delay and the backend requeues; there is no blocking `sleep()` in the worker and no double-retry. Configure backoff with `retryBackoffStrategy` (`none` / `fixed` / `exponential`), `retryBackoffBase`, `retryBackoffMultiplier`, `retryBackoffMax`, `retryBackoffJitter`.
- **Real timeouts**: with `ext-pcntl`, a per-attempt timeout interrupts even tight loops; without it, a documented soft fallback applies.
- **Idempotency** (opt-in): set `idempotencyKey()`; the `IdempotencyGuard` deduplicates via cache for `$idempotencyTtl` seconds.
- **Single-instance lock** with an ownership token, **circuit breaker**, per-queue **rate limits** (`$queueRateLimits`) and an optional **dead-letter queue** (`$deadLetterQueue`).

See [Retries](docs/RETRIES.md) and [Architecture](docs/ARCHITECTURE.md).

Configuration
-------------

[](#configuration)

All options live in `Config\Jobs` (`handlers`, `queueHandlers`, `backends`, `worker`, `queues`, retry/backoff, timeouts, signing, rate limits, DLQ, backend-specific settings). See [Configuration](docs/CONFIGURATION.md).

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT © Daycry

###  Health Score

49

—

FairBetter than 94% of packages

Maintenance94

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Recently: every ~28 days

Total

22

Last Release

30d ago

Major Versions

v1.2.0 → v2.0.02026-05-07

v2.0.0 → v3.0.02026-06-04

### Community

Maintainers

![](https://www.gravatar.com/avatar/3b0f66565d5c9ca3c84fb294e04f8d5e0b9a867d9c06f83b95bf168bd6fcf9bc?d=identicon)[daycry](/maintainers/daycry)

---

Top Contributors

[![daycry](https://avatars.githubusercontent.com/u/7590335?v=4)](https://github.com/daycry "daycry (76 commits)")

### Embed Badge

![Health badge](/badges/daycry-jobs/health.svg)

```
[![Health](https://phpackages.com/badges/daycry-jobs/health.svg)](https://phpackages.com/packages/daycry-jobs)
```

###  Alternatives

[laravel/framework

The Laravel Framework.

34.8k543.8M20.1k](/packages/laravel-framework)[spatie/laravel-health

Monitor the health of a Laravel application

87512.0M165](/packages/spatie-laravel-health)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M203](/packages/sulu-sulu)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.6M572](/packages/shopware-core)[contao/core-bundle

Contao Open Source CMS

1231.6M2.8k](/packages/contao-core-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
