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).
210 changes: 210 additions & 0 deletions docs/guide/en/channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# 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.

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 a different backend for long-running jobs.

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

## How channels are used in the code

- A channel name is passed to `Yiisoft\Queue\Provider\QueueProviderInterface::get($channel)`.
Copy link
Member

Choose a reason for hiding this comment

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

As a user, why do I need internal details in the docs? Is that needed to use queue?

- The provider returns a `Yiisoft\Queue\QueueInterface` instance bound to 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.

## Choosing a channel at runtime

### In CLI

These built-in commands accept channel names:

- `queue:listen [channel]` listens to a single channel (defaults to `yii-queue`).
- `queue:run [channel1 [channel2 [...]]]` processes existing messages and exits.
- `queue:listen-all [channel1 [channel2 [...]]]` iterates over multiple channels (meant mostly for development).

Examples:

```sh
yii queue:listen emails
yii queue:run critical emails --maximum=100
yii queue:listen-all critical emails --pause=1 --maximum=500
```

### In PHP code

When you have a `QueueProviderInterface`, request a queue by channel name:

```php
/** @var \Yiisoft\Queue\Provider\QueueProviderInterface $provider */

$emailsQueue = $provider->get('emails');
$emailsQueue->push(new \Yiisoft\Queue\Message\Message('send-email', ['to' => 'user@example.com']));
```

You can also check if a channel exists before trying to get it:

```php
if ($provider->has('emails')) {
$emailsQueue = $provider->get('emails');
}
```

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

`QueueProviderInterface::get()` may throw:

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

## Providers

`QueueProviderInterface` is the component responsible for returning a `QueueInterface` instance bound to a particular channel.

Out of the box, this package provides three implementations:

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

### `AdapterFactoryQueueProvider`

This provider creates channel-specific `QueueInterface` instances based on adapter definitions.

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');
```

## Configuration with yiisoft/config

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

By default, `QueueProviderInterface` is bound to `AdapterFactoryQueueProvider`.
That makes `yiisoft/queue.channels` a strict channel registry:

- `QueueProviderInterface::has($channel)` checks whether the channel exists in definitions.
- `QueueProviderInterface::get($channel)` throws `ChannelNotFoundException` for unknown channels.

The same channel list is used by `queue:run` and `queue:listen-all` as the default set of channels to process.

It is a map:

- key: channel name
- value: adapter definition that should be resolved for that channel

Minimal example (single channel):

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

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

Multiple channels example:

```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,
],
],
];
```

The exact adapter definitions depend on which queue adapter package you use (Redis, AMQP, etc.).

When using the default DI config from this package, the configured channel names are also used as the default channel list for `queue:run` and `queue:listen-all`.

## 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:

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

$definitions = [
'channel1' => new SynchronousAdapter($worker, $queue),
'channel2' => static fn (SynchronousAdapter $adapter) => $adapter->withChannel('channel2'),
'channel3' => [
'class' => SynchronousAdapter::class,
'__constructor' => ['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