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)
103 changes: 103 additions & 0 deletions docs/guide/en/callable-definitions-extended.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Callable Definitions Extended

Callable definitions in `yiisoft/queue` are based on [native PHP callables](https://www.php.net/manual/en/language.types.callable.php) and suggest additonial ways to define a callable.

Both definition types (classic callables and new ones) allow you to use a DI container and [yiisoft/injector](https://github.com/yiisoft/injector) to resolve dependencies in a lazy way.
These callable definition formats are used across the package to convert configuration definitions into real callables.

## Type 1: Native PHP callable

When you define a callable as a native PHP callable, it is not modified in any way and is called as is. The only difference is that you can declare a dependency list as its parameter list, which will be resolved via [yiisoft/injector](https://github.com/yiisoft/injector) and a DI container.
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(MyDependency $dependency) {
// 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, extended callable definitions behave exactly like native callables. But there is a major difference:
all the objects are instantiated automatically by 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:
```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).
241 changes: 241 additions & 0 deletions docs/guide/en/channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# Queue channels

A *queue channel* is a named queue configuration (a logical namespace/identifier that separates one queue workload 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

The sections below describe internal mechanics and advanced setups. You can skip them if you only need to configure and use channels.

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