-
-
Notifications
You must be signed in to change notification settings - Fork 27
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
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,210 @@ | ||
| # 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. | ||
|
|
||
| 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)`. | ||
|
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. 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). | ||
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.