PHPackages                             phly/phly-redis-task-queue - 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. [Logging &amp; Monitoring](/categories/logging)
4. /
5. phly/phly-redis-task-queue

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

phly/phly-redis-task-queue
==========================

0.4.5(2y ago)0128BSD-2-ClausePHPPHP ~8.1.0 || ~8.2.0

Since Mar 9Pushed 2y ago1 watchersCompare

[ Source](https://github.com/phly/phly-redis-task-queue)[ Packagist](https://packagist.org/packages/phly/phly-redis-task-queue)[ RSS](/packages/phly-phly-redis-task-queue/feed)WikiDiscussions 0.4.x Synced 1mo ago

READMEChangelogDependencies (12)Versions (13)Used By (0)

phly/phly-redis-task-queue
==========================

[](#phlyphly-redis-task-queue)

Implements both a task queue and crontab runner utilizing Redis, a [PSR-14 EventDispatcher](https://www.php-fig.org/psr/psr-14/), ReactPHP's event loop, and symfony/console (via [laminas-cli](https://docs.laminas.dev/laminas-cli/)).

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

[](#installation)

```
composer require phly/phly-redis-task-queue
```

I recommend having ext-event installed for best results.

Additionally, the component marks itself as a [Laminas component](https://docs.laminas.dev/laminas-component-installer), and usage in a Laminas or Mezzio application will automatically setup wiring for usage.

Usage
-----

[](#usage)

### Creating tasks

[](#creating-tasks)

Tasks are objects of any class.

As an example:

```
namespace Foo;

class HelloWorldTask
{
    public function __construct(
        public readonly string $message
    ) {
    }
}
```

### Serializing tasks

[](#serializing-tasks)

In order to queue a task and later dispatch it, you need to be able to serialize and deserialize it. To do this, you will need to create a *mapper*, which is a class implementing `Phly\RedisTaskQueue\Mapper\MapperInterface`:

```
interface MapperInterface
{
    /**
     * Can this implementation hydrate the given array type?
     *
     * @psalm-param array{__type: string, ...} $serialized
     */
    public function handlesArray(array $serialized): bool;

    /**
     * Can this implementation extract the given object type?
     */
    public function handlesObject(object $object): bool;

    /** @return array{__type: string, ...} */
    public function castToArray(object $object): array;

    /** @psalm-param array{__type: string, ...} $serialized */
    public function castToObject(array $serialized): object;
}
```

When serializing to an array, the returned array MUST contain the member `__type`, with a string value resolving to the task class type. Otherwise, the remainder of the serialization format is up to you. You can assume that when `hydrate` is called, the array contains a `__type` member your mapper can handle.

These mapper classes can be registered with your DI container. Once you have, you can register them with your configuration so that they will be automatically added to the internal mapper:

```
return [
    'redis-task-queue' => [
        'mappers' => [
            \App\Mapper\TaskMapper::class,
        ],
    ],
];
```

#### EmptyObjectMapper

[](#emptyobjectmapper)

This component provides a versatile mapper implementation for empty task implementations, `Phly\RedisTaskQueue\Mapper\EmptyObjectMapper`. This class takes a single argument, the name of a task class to respond to. When extracting, it will extract an array with exactly one member, `__type`, with the class value. When hydrating, it will instantiate the given class with no arguments and return it.

Because this implementation has an argument, you have two possibilities for registering it with the mapper.

First, you could create a custom factory. As an example, if the empty class were named `RssFeed`, you could create an `RssFeedMapperFactory`:

```
class RssFeedMapperFactory
{
    public function __invoke(): EmptyObjectMapper
    {
        return new EmptyObjectMapper(RssFeed::class);
    }
}
```

and then map a service to it:

```
return [
    'dependencies' => [
        'factories' =>
            'rss-feed-mapper' => RssFeedMapperFactory::class,
        ],
    ],
    'redis-task-queue' => [
        'mappers' => [
            'rss-feed-mapper',
        ],
    ],
];
```

Second, you could use a delegator factory on the `Phly\RedisTaskQueue\Mapper\Mapper` class to attach it:

```
class RssFeedMapperDelegator
{
    public function __invoke(ContainerInterface $container, string $requestedName, callable $factory): Mapper
    {
        $mapper = $factory();
        $mapper->attach(new EmptyObjectMapper(RssFeed::class));
        return $mapper;
    }
}
```

You would then just register the delegator factory:

```
return [
    'dependencies' => [
        'delegators' => [
            \Phly\RedisTaskQueue\Mapper\Mapper::class => [
                RssFeedMapperDelegator::class,
            ],
        ],
    ],
];
```

### Enqueuing tasks

[](#enqueuing-tasks)

I recommend decoupling your application from the `RedisTaskQueue`, and instead use a PSR-14 dispatcher to dispatch an event wrapping the task. This approach means that in development, you can have an alternate handler for deferred events that, for instance, logs the task, versus actually enqueueing it. Additionally, by wrapping the task in a `DeferredEvent`, you will be signaling in your code that you expect this to happen asynchronously, versus immediately. If you later decide to handle such tasks immediately, you can use a different listener for `DeferredEvent`s, or you can unwrap specific tasks from `DeferredEvent`.

To enqueue a task, dispatch it by wrapping it in a `Phly\RedisTaskQueue\EventDispatcher\DeferredEvent`:

```
$dispatcher->dispatch(new DeferredEvent($task));
```

A listener for this event is provided in this component: `Phly\RedisTaskQueue\EventDispatcher\DeferredEventListener`. You will need to wire this to your PSR-14 dispatcher.

> #### Decoupling your application from this library for purposes of deferment
>
> [](#decoupling-your-application-from-this-library-for-purposes-of-deferment)
>
> If you want to "own" the application code that would defer tasks, and not have it depend on this component, you can do so by defining your own `DeferredEvent` or `AsyncEvent` type, and then creating your own PSR-14 listener for that type. The implementation would look like the [DeferredEventListener](./src/EventDispatcher/DeferredEventListener.php) in this library.

### Processing tasks

[](#processing-tasks)

You will need to register one or more listeners for each task type you will queue with the event dispatcher. As an example, building on the above, you might have the following listener:

```
namespace Foo;

class HelloWorldListener
{
    public function __invoke(HelloWorldTask $task): void
    {
        error_log(sprintf('Hello, %s', $task->message));
    }
}
```

You would then register this via a PSR-14 listener provider.

### Running the task runner

[](#running-the-task-runner)

```
./vendor/bin/laminas phly:redis-task-queue:task-worker
```

If you want a pool of task workers, I recommend [supervisord](https://supervisord.org). Configuration for a pool with five workers might look like this:

```
[program:worker]
autostart=true
autorestart=unexpected
command=vendor/bin/laminas phly:redis-task-queue:task-worker
; Change the following to your application root:
directory=/var/www
numprocs=5
process_name=%(program_name)s_%(process_num)d
redirect_stderr=true
```

Crontabs
--------

[](#crontabs)

The crontab implementation in this library is via the `phly:redis-task-queue:cron-runner` laminas-cli command. It pulls crontab definitions from your application configuration, and then once a minute checks to see if any tasks are due. If so, it enqueues the related task.

### Configuration

[](#configuration)

Configuration is via the "cron.jobs" configuration key. Each element is an array with two keys:

- **schedule**: the crontab schedule to use; see the [dragonmantank/cron-expression write-up](https://github.com/dragonmantank/cron-expression#cron-expressions) for a good overview.
- **task**: a JSON string representing a task to run. This string MUST represent a JSON object that can be mapped to a PHP class using the mapper. Due to how JSON parsing works, you will need to ensure you escape namespace separators properly; this is usually a sequence of four backslashes: `App\\\\Tasks\\\\FetchRssFeed`.

As an example:

```
return [
    'cron' => [
        // Keys are not required for jobs, but are helpful when debugging configuration
        'rss' => [
            // Fetch every 3 hours at the top of the hour
            'schedule' => '0 */3 * * *',
            'task'     => '{"__type": "App\\\\Tasks\\\\FetchRssFeed", "url": "https://github.com/weierophinney", "headers": {"Accept": "application/atom+xml"}}',
        ],
        'social' => [
            // Fetch every 15 minutes
            'schedule' => '*/15 * * * *',
            'task' => '{"__type": "App\\\\Tasks\\\\FetchSocial"}',
        ],
    ],
];
```

### Invoking the cron-runner

[](#invoking-the-cron-runner)

To invoke the cron-runner, use the following:

```
./vendor/bin/laminas phly:redis-task-queue:cron-runner
```

I recommend running this with [supervisord](https://supervisord.org). When you do, use **ONLY ONE** worker, to ensure that only one task is queued when it comes due. Configuration would look like the following:

```
[program:cron]
autostart=true
autorestart=unexpected
command=vendor/bin/laminas phly:redis-task-queue:cron-runner
; Change the following to your application root:
directory=/var/www
numprocs=1
redirect_stderr=true
```

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

[](#configuration-1)

The following configuration can be consumed via the `config` service:

```
return [
    'redis-task-queue' => [
        'mappers' => [
            // Class names of mapper services that can map event types for serialization
        ],
        // Float seconds interval between task runner invocations
        'task_runner_interval' => 1.0,
        'signals' => [
            // Integer signals which indicate the task or cron runner should terminate.
            // In some cases, you may not be able to listen to SIGKILL
            // (e.g. running under a non-privileged user in supervisord)
            SIGKILL,
            SIGINT,
            SIGTERM,
        ],
    ],
    'cron' => [
        'jobs' => [
            /* Job definitions.
             * These are each arrays, and can have a named index or not.
             * Each has the following structure:
             * [
             *     'schedule' => '* * * * *', // valid cron schedule string
             *     'task'     => '{"__type": "...", ...}' // JSON serialization of task to run
             * ]
             */
        ],
    ],
];
```

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity10

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity55

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 ~13 days

Recently: every ~40 days

Total

13

Last Release

994d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/25943?v=4)[Matthew Weier O'Phinney](/maintainers/weierophinney)[@weierophinney](https://github.com/weierophinney)

---

Top Contributors

[![weierophinney](https://avatars.githubusercontent.com/u/25943?v=4)](https://github.com/weierophinney "weierophinney (31 commits)")

---

Tags

psr-3consolePSR-11laminascomponentspsr-14redisqueuecron

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phly-phly-redis-task-queue/health.svg)

```
[![Health](https://phpackages.com/badges/phly-phly-redis-task-queue/health.svg)](https://phpackages.com/packages/phly-phly-redis-task-queue)
```

###  Alternatives

[laravel/framework

The Laravel Framework.

34.6k509.9M17.0k](/packages/laravel-framework)[sulu/sulu

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

1.3k1.3M152](/packages/sulu-sulu)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[mezzio/mezzio-swoole

Swoole support for Mezzio

92238.9k3](/packages/mezzio-mezzio-swoole)[laminas/laminas-cli

Command-line interface for Laminas projects

563.7M54](/packages/laminas-laminas-cli)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6939.5M341](/packages/drupal-core-recommended)

PHPackages © 2026

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