Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 61 additions & 244 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions config/params.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
'commands' => [
'queue:run' => RunCommand::class,
'queue:listen' => ListenCommand::class,
'queue:listen-all' => ListenAllCommand::class,
'queue:listen:all' => ListenAllCommand::class,
],
],
Expand Down
35 changes: 32 additions & 3 deletions docs/guide/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,39 @@

An extension for running tasks asynchronously via queues.

## Guides and concept explanations
## Getting started

- [Prerequisites and installation](prerequisites-and-installation.md)
- [Configuration with yiisoft/config](configuration-with-config.md)
- [Manual configuration](configuration-manual.md)

- [Usage basics](usage.md)
- [Migrating from `yii2-queue`](migrating-from-yii2-queue.md)
- [Errors and retryable jobs](error-handling.md)
- [Workers](worker.md)
- [Console commands](console-commands.md)

## Adapters

- [Adapter list](adapter-list.md)
- [Synchronous adapter](adapter-sync.md)

## Core concepts

- [Queue channels](channels.md)
- [Message handler](message-handler.md)
- [Envelopes](envelopes.md)
- [Middleware pipelines](middleware-pipelines.md)
- [Loops](loops.md)

## Interoperability

- [Producing messages from external systems](producing-messages-from-external-systems.md)

## Reliability and visibility

- [Errors and retryable jobs](error-handling.md)
- [Job status](job-status.md)
- [Yii Debug integration](debug-integration.md)

## Migration from Yii2

- [Migrating from `yii2-queue`](migrating-from-yii2-queue.md)
100 changes: 100 additions & 0 deletions docs/guide/en/callable-definitions-extended.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Callable Definitions Extended

Сallable definitions in `yiisoft/queue` extend [native PHP callables](https://www.php.net/manual/en/language.types.callable.php). That means, there are two types of definitions. Nevertheless, each of them may define dependency list in their parameter lists, which will be resolved via [yiisoft/injector](https://github.com/yiisoft/injector) and a DI Container.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "Сallable" starts with a Cyrillic character 'С' (U+0421) instead of the Latin 'C' (U+0043). This should be "Callable" with a Latin C.

Suggested change
Сallable definitions in `yiisoft/queue` extend [native PHP callables](https://www.php.net/manual/en/language.types.callable.php). That means, there are two types of definitions. Nevertheless, each of them may define dependency list in their parameter lists, which will be resolved via [yiisoft/injector](https://github.com/yiisoft/injector) and a DI Container.
Callable definitions in `yiisoft/queue` extend [native PHP callables](https://www.php.net/manual/en/language.types.callable.php). That means, there are two types of definitions. Nevertheless, each of them may define dependency list in their parameter lists, which will be resolved via [yiisoft/injector](https://github.com/yiisoft/injector) and a DI Container.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sentence isn't clear. I read it as "callable definitions extend native PHP callables so there are two types of definitions because of that".

It is used across the package to convert configuration definitions into real callables.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It = injector or container?


## Type 1: Native PHP callable

When you define a callable in a such manner, they are not modified in any way and are called as is. An only difference is that you can define dependency list in their parameter lists, which will be resolved via [yiisoft/injector](https://github.com/yiisoft/injector) and a DI Container.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which "such manner"? Do you mean native PHP callable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are they who are not modified? Callables? Parameters?

As you can see in the [PHP documentation](https://www.php.net/manual/en/language.types.callable.php), there are several ways to define a native callable:

- **Closure (lambda function)**. It may be static. Example:
```php
$callable = static function(Update $update) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is Update here?

// do stuff
}
```
- **First class callable**. It's a Closure too, BTW ;) Example:
```php
$callable = trim(...);
$callable2 = $this->foo(...);
```
- **A class static function**. When a class has a static function, an array syntax may be used:
```php
$callable = [Foo::class, 'bar']; // this will be called the same way as Foo::bar();
```
- **An object method**. The same as above, but with an object and a non-static method:
```php
$foo = new Foo();
$callable = [$foo, 'bar']; // this will be called the same way as $foo->bar();
```
- **A class static function as a string**. I don't recommend you to use this ability, as it's non-obvious and
hard to refactor, but it still exists:
```php
$callable = 'Foo::bar'; // this will be called the same way as Foo::bar();
```
- **A name of a named function**:
```php
function foo() {
// do stuff
}
$callable = 'foo';
$callable2 = 'array_map';
```
- **Callable objects**. An object with [the `__invoke` method](https://www.php.net/manual/en/language.oop5.magic.php#object.invoke) implemented:
```php
class Foo
{
public function __invoke()
{
// do stuff
}
}

$callable = new Foo();
```

## Type 2: Callable definition extensions (via container)

Under the hood, this extension behaves exactly like the **Type 1** ones. But there is a major difference too:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which "this extension"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's "type 1"?

all the objects are instantiated automatically with a PSR-11 DI Container with all their dependencies
and in a lazy way (only when they are really needed).
Ways to define an extended callable:

- An object method through a class name or alias:
```php
final readonly class Foo
{
public function __construct(private MyHeavyDependency $dependency) {}

public function bar()
{
// do stuff
}
}

$callable = [Foo::class, 'bar'];
```
Here is a simplified example of how it works:
```php
if ($container->has($callable[0])) {
$callable[0] = $container->get($callable[0])
}

$callable();
```
- Class name of an object with [the `__invoke` method](https://www.php.net/manual/en/language.oop5.magic.php#object.invoke) implemented:
```php
$callable = Foo::class;
```
It works the same way as above: an object will be retrieved from a DI container and called as a function.

_Note: you can use an alias registered in your DI Container instead of a class name._ This will also work if you have a "class alias" definition in container:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```php
$callable = 'class alias'; // for a "callable object"
$callable2 = ['class alias', 'foo']; // to call "foo" method of an object found by "class alias" in DI Container
```

## Invalid definitions

The factory throws `Yiisoft\Queue\Middleware\InvalidCallableConfigurationException` when it cannot create a callable (for example: `null`, unsupported array format, missing method, container entry is not callable).
239 changes: 239 additions & 0 deletions docs/guide/en/channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Queue channels

A *queue channel* is a named queue configuration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that more like a namespace that separates one logical queue from another?


In practice, a channel is a string (for example, `yii-queue`, `emails`, `critical`) that selects which queue backend (adapter) messages are pushed to and which worker consumes them.

At a high level:

- You configure one or more channels.
- When producing messages, you either:
- use `QueueInterface` directly (single/default channel), or
- use `QueueProviderInterface` to get a queue for a specific channel.
- When consuming messages, you run a worker command for a channel (or a set of channels).

Having multiple channels is useful when you want to separate workloads, for example:

- **Different priorities**: `critical` vs `low`.
- **Different message types**: `emails`, `reports`, `webhooks`.
- **Different backends / connections**: fast Redis queue for short jobs and RabbitMQ backend for long-running jobs or inter-app communication.

The default channel name is `Yiisoft\Queue\QueueInterface::DEFAULT_CHANNEL` (`yii-queue`).

## Quick start (yiisoft/config)

When using [yiisoft/config](https://github.com/yiisoft/config), channel configuration is stored in params under `yiisoft/queue.channels`.

### 1. Start with a single channel (default)

If you use only a single channel, you can inject `QueueInterface` directly.

#### 1.1 Configure an Adapter

Adapter is what actually sends messages to a queue broker.

Minimal DI configuration example:

```php
use Yiisoft\Queue\Adapter\SynchronousAdapter;
use Yiisoft\Queue\Adapter\AdapterInterface;

return [
AdapterInterface::class => SynchronousAdapter::class,
];
```
> `SynchronousAdapter` is for learning/testing only. For production, install a real adapter, see adapter list: [adapter-list](adapter-list.md).

#### 1.2. Configure a default channel

When you are using `yiisoft/config` and the default configs from this package are loaded, the default channel is already present in params (so you don't need to add anything). The snippet below shows what is shipped by default in [config/params.php](../../../config/params.php):

```php
use Yiisoft\Queue\Adapter\AdapterInterface;
use Yiisoft\Queue\QueueInterface;

return [
'yiisoft/queue' => [
'channels' => [
QueueInterface::DEFAULT_CHANNEL => AdapterInterface::class,
],
],
];
```

Pushing a message via DI:

```php
use Yiisoft\Queue\QueueInterface;
use Yiisoft\Queue\Message\Message;

final readonly class SendWelcomeEmail
{
public function __construct(private QueueInterface $queue)
{
}

public function run(string $email): void
{
$this->queue->push(new Message('send-email', ['to' => $email]));
}
}
```

### 2. Multiple channels

Add more channels to the `params.php`:

```php
use Yiisoft\Queue\QueueInterface;

return [
'yiisoft/queue' => [
'channels' => [
QueueInterface::DEFAULT_CHANNEL => \Yiisoft\Queue\Adapter\AdapterInterface::class,
'critical' => \Yiisoft\Queue\Adapter\AdapterInterface::class,
'emails' => \Yiisoft\Queue\Adapter\AdapterInterface::class,
],
],
];
```

If you have multiple channels, inject `QueueProviderInterface` and call `get('channel-name')`.

```php
use Yiisoft\Queue\Provider\QueueProviderInterface;
use Yiisoft\Queue\Message\Message;

final readonly class SendTransactionalEmail
{
public function __construct(private QueueProviderInterface $queueProvider)
{
}

public function run(string $email): void
{
$this->queueProvider
->get('emails')
->push(new Message('send-email', ['to' => $email]));
}
}
```

`QueueProviderInterface` accepts both strings and `BackedEnum` values (they are normalized to a string channel name).

## Running workers (CLI)

To consume messages you run console commands such as `queue:run`, `queue:listen`, and `queue:listen-all`.
See [Console commands](console-commands.md) for details.

## Advanced

### How channels are used in the code

- A channel name is passed to `Yiisoft\Queue\Provider\QueueProviderInterface::get($channel)`.
- The provider returns a `Yiisoft\Queue\QueueInterface` instance that uses an adapter configured for that channel.
- Internally, the provider creates an adapter instance and calls `AdapterInterface::withChannel($channel)`.

In other words, a channel is the key that lets the application select a particular adapter instance/configuration.

`QueueInterface::getChannel()` is available for introspection and higher-level logic (for example, selecting middleware pipelines per channel). The channel itself is stored in the adapter and `Queue` proxies it.

### Providers

`QueueProviderInterface::get()` may throw:

- `Yiisoft\Queue\Provider\ChannelNotFoundException`
- `Yiisoft\Queue\Provider\InvalidQueueConfigException`
- `Yiisoft\Queue\Provider\QueueProviderException`

Out of the box, this package provides three implementations:

- `Yiisoft\Queue\Provider\AdapterFactoryQueueProvider`
- `Yiisoft\Queue\Provider\PrototypeQueueProvider`
- `Yiisoft\Queue\Provider\CompositeQueueProvider`

#### `AdapterFactoryQueueProvider` (default)

`AdapterFactoryQueueProvider` is used by default when you use `yiisoft/config`.
It creates `QueueInterface` instances based on adapter definitions indexed by channel name.

It uses [`yiisoft/factory`](https://github.com/yiisoft/factory) to resolve adapter definitions.

This approach is recommended when you want:

- Separate configuration per channel.
- Stronger validation (unknown channels are not silently accepted).

#### `PrototypeQueueProvider`

This provider always returns a queue by taking a base queue + base adapter and only changing the channel name.

This can be useful when all channels use the same adapter and only differ by channel name.

This strategy is not recommended as it does not give you any protection against typos and mistakes in channel names.

Example:

```php
use Yiisoft\Queue\Provider\PrototypeQueueProvider;

$provider = new PrototypeQueueProvider($queue, $adapter);

$queueForEmails = $provider->get('emails');
$queueForCritical = $provider->get('critical');
```

#### `CompositeQueueProvider`

This provider combines multiple providers into one.

It tries to resolve a channel by calling `has()`/`get()` on each provider in the order they are passed to the constructor.
The first provider that reports it has the channel wins.

Example:

```php
use Yiisoft\Queue\Provider\CompositeQueueProvider;

$provider = new CompositeQueueProvider(
$providerA,
$providerB,
);

$queue = $provider->get('emails');
```

### Manual configuration (without yiisoft/config)

For multiple channels without `yiisoft/config`, you can create a provider manually.

`AdapterFactoryQueueProvider` accepts adapter definitions indexed by channel names and returns a `QueueInterface` for a channel on demand:

> In this example, `$worker`, `$queue` and `$container` are assumed to be created already.
> See [Manual configuration](configuration-manual.md) for a full runnable setup.

```php
use Yiisoft\Queue\Provider\AdapterFactoryQueueProvider;
use Yiisoft\Queue\Adapter\SynchronousAdapter;

$definitions = [
'channel1' => new SynchronousAdapter($worker, $queue, 'channel1'),
'channel2' => new SynchronousAdapter($worker, $queue, 'channel2'),
'channel3' => [
'class' => SynchronousAdapter::class,
'__construct()' => ['channel' => 'channel3'],
],
];

$provider = new AdapterFactoryQueueProvider(
$queue,
$definitions,
$container,
);

$queueForChannel1 = $provider->get('channel1');
$queueForChannel2 = $provider->get('channel2');
$queueForChannel3 = $provider->get('channel3');
```

For more information about the definition formats available, see the [`yiisoft/factory` documentation](https://github.com/yiisoft/factory).
Loading
Loading