-
-
Notifications
You must be signed in to change notification settings - Fork 28
Documentation #250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Documentation #250
Changes from all commits
745b353
eb27a90
ac1a2f8
1fb3057
91bb7cf
2feaf14
604f2c0
aee4f72
7821be0
4fe7321
23e7997
ae5867a
f2e7eff
c598caf
0f05e07
dd3ae30
dd30d79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which "such manner"? Do you mean native PHP callable?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is |
||
| // 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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which "this extension"?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,239 @@ | ||
| # Queue channels | ||
|
|
||
| A *queue channel* is a named queue configuration. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). | ||
There was a problem hiding this comment.
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.