You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/5.x/extend/queue-jobs.md
+35-22Lines changed: 35 additions & 22 deletions
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@ This feature relies on [Yii’s queue system](https://www.yiiframework.com/exten
11
11
- The ability to set a fallback description for the job.
12
12
- The ability to label and report task progress for a better user experience.
13
13
14
-
## To Queue or Not to Queue
14
+
## To Queue or Not to Queue?
15
15
16
16
An ideal queue job is something slow that the a user shouldn’t have to wait on while using a site’s front end or the control panel. Multi-step processes and actions that connect with third-party APIs can be ideal candidates for queueing.
17
17
@@ -40,15 +40,15 @@ class MyJob extends \craft\queue\BaseJob
40
40
$message = new Message();
41
41
42
42
$message->setTo('to@domain.tld');
43
-
$message->setSubject('Oh Hai');
44
-
$message->setTextBody('Hello from the queue system! 👋');
43
+
$message->setSubject('Craft Queue Test');
44
+
$message->setTextBody('Hello from the Craft queue worker! 👋');
45
45
46
46
Craft::$app->getMailer()->send($message);
47
47
}
48
48
49
49
protected function defaultDescription(): string
50
50
{
51
-
return Craft::t('app', 'Sending a worthless email');
51
+
return Craft::t('app', 'Sending a test email');
52
52
}
53
53
}
54
54
```
@@ -63,16 +63,14 @@ You can do this with BaseJob’s [`setProgress()`](craft5:craft\queue\BaseJob::s
63
63
- a number between 0 and 1 representing the percent complete
64
64
- an optional, human-friendly label describing the progress
65
65
66
-
We can modify our example `execute()` method to send an email to every Craft user and report the job’s progress.
67
-
68
-
::: danger
69
-
Do not lightheartedly send an email to every Craft user. Not cool.
70
-
:::
66
+
We can modify our example `execute()` method to send an email to a number of users and report the job’s progress between each send.
71
67
72
68
```php{7-14}
73
69
public function execute($queue): void
74
70
{
75
-
$users = \craft\elements\User::findAll();
71
+
$users = \craft\elements\User::find()
72
+
->admin(false)
73
+
->all();
76
74
$totalUsers = count($users);
77
75
78
76
foreach ($users as $i => $user) {
@@ -88,8 +86,8 @@ public function execute($queue): void
88
86
$message = new Message();
89
87
90
88
$message->setTo($user->email);
91
-
$message->setSubject('Oh Hai');
92
-
$message->setTextBody('Hello from the queue system! 👋');
89
+
$message->setSubject('Craft Queue Test');
90
+
$message->setTextBody('Hello from the Craft queue worker! 👋');
93
91
94
92
// Swallow exceptions from the mailer:
95
93
try {
@@ -101,29 +99,31 @@ public function execute($queue): void
101
99
}
102
100
```
103
101
102
+
Depending on the number of users (or any kind of record or item) and the app’s mailer configuration, this may take longer than the queue’s allowed <abbrtitle="Time to reserve">TTR</abbr>. Consider [batching](#batched-jobs) jobs that act on many individual items!
103
+
104
104
### Dealing with Failed Jobs
105
105
106
106
In our first example, exceptions from the mailer can bubble out of our job—but in the second example, we catch those errors so the job is not halted prematurely.
107
107
108
-
This decision is up to you: if the work in a job is nonessential (or will be done again later, like <craft5:craft\queue\jobs\GeneratePendingTransforms>), you can catch and log errors and let the job end nominally; if the work is critical (like synchronizing something to an external API), it may be better to let the exception bubble out of `execute()`.
108
+
This decision is up to you: if the work in a job is nonessential (or can be done again later without consequence, as is the case with <craft5:craft\queue\jobs\GeneratePendingTransforms>), you can catch errors, log them, and let the job exit nominally; if the work is critical (like synchronizing something to or from an external API), it may be better to let the exception bubble out of `execute()`.
109
109
110
110
The queue wraps every job in its own `try` block, and will mark any jobs that throw exceptions as _failed_. The exception message that caused the failure will be recorded along with the job. Failed jobs can be retried from the control panel or with the `php craft queue/retry [id]` command.
111
111
112
112
#### Retryable Jobs
113
113
114
-
The queue will automatically retry failed jobs that implement the [`RetryableJobInterface`](https://www.yiiframework.com/extension/yiisoft/yii2-queue/doc/guide/2.0/en/retryable#retryablejobinterface). A job will only be retried after its `ttr`has passed—even if it didn’t use up the allowed time, and will be marked as failed once `canRetry()` returns false.
114
+
The queue will automatically retry failed jobs that implement [`RetryableJobInterface`](https://www.yiiframework.com/extension/yiisoft/yii2-queue/doc/guide/2.0/en/retryable#retryablejobinterface). A job will only be retried after its “time to reserve” has passed—even if it didn’t use up the allowed time—and will be marked as failed once `canRetry()` returns false.
115
115
116
116
::: warning
117
-
Returning `true` from `canRetry()`can pollute your queue with jobs that may never succeed. Failed jobs are not necessarily bad! Exceptions can be used to track failures in code that runs unattended.
117
+
Unconditionally returning `true` from `canRetry()`will pollute your queue with jobs that may never succeed. Failed jobs are not necessarily bad—exceptions can (and should) be thrown to track failures in code that runs unattended!
118
118
:::
119
119
120
120
### Batched Jobs
121
121
122
122
In situations where there is simply too much work to do in a single request (or within PHP’s memory limit), consider extending <craft5:craft\queue\BaseBatchedJob>.
123
123
124
-
Batched jobs’ `execute()` method is handled for you. Instead, you must define two new methods:
124
+
Batched jobs’ `execute()` method is implemented for you. Instead, you must define two new methods:
125
125
126
-
-`loadData()` — Returns a class implementing <craft5:craft\base\Batchable>, like <craft5:craft\db\QueryBatcher>. Data is not necessarily loaded at this point, but a means of fetching data in “slices” must be.
126
+
-`loadData()` — Returns a class implementing <craft5:craft\base\Batchable>, like <craft5:craft\db\QueryBatcher>. Data is not necessarily _loaded_ at this point—instead, you’ll return a means of fetching data in “slices” or “pages.”
127
127
-`processItem($item)` — Your logic for handling a single item in each batch.
128
128
129
129
::: tip
@@ -143,23 +143,36 @@ return new QueryBatcher($query);
143
143
Also note that we’re explicitly ordering by `id`—this can help avoid skipped or double-processed items across batches when the underlying data changes (including changes made _within a job_)!
144
144
:::
145
145
146
-
Batched jobs can also define a default `$batchSize` that is appropriate for the workload. The batch size is not a guaranteed value, but a target when Craft spawns the next job—it will keep track of memory usage and _may_ stop short, scheduling the next batch to resume where it left off.
146
+
Batched jobs can also define a default `$batchSize` that is appropriate for the workload. The batch size is a _maximum_ value, carried from job to job—Craft keeps track of memory usage and _may_ stop short and push a new job for the next batch. Every job maintains an `itemOffset` so they can be resumed exactly where the last one left off.
147
147
148
148
::: warning
149
149
Batched jobs **must** be pushed using <craft5:craft\helpers\Queue::push()>, or `delay` and `ttr` settings may be lost for subsequent batches.
150
150
:::
151
151
152
+
#### Batch Hooks
153
+
154
+
Special setup and cleanup actions can be defined in two pairs of methods:
155
+
156
+
| Method… | Runs… |
157
+
|`before()`| …at the very beginning of a series of batched jobs. |
158
+
|`beforeBatch()`| …prior to each batch in a series. |
159
+
|`afterBatch()`| …after each batch in a series. |
160
+
|`after()`| …at the very end of a series of batched jobs. |
161
+
162
+
`before()` and `after()` are invoked _once_ per manually-pushed batched job, whereas `beforeBatch()` and `afterBatch()` are run for _every_ slice. If all the items in a batched job are processed in a single job, it will invoke all four hooks.
163
+
152
164
## Adding Your Job to the Queue
153
165
154
-
Once you’ve created your job, you can add it to the queue:
166
+
Once you’ve created your job, add it to the queue with the helper:
155
167
156
-
```php
168
+
```php{4}
169
+
use craft\helpers\Queue;
157
170
use mynamespace\queue\jobs\MyJob;
158
171
159
-
\craft\helpers\Queue::push(new MyJob());
172
+
Queue::push(new MyJob());
160
173
```
161
174
162
-
You can do this wherever it makes sense—most likely in a controller action or service.
175
+
Users and developers typically cannot (and should not need to) trigger specific jobs at-will, so it’s important that your plugin push jobs where it makes sense—typically in a [controller action](controllers.md) or [service](services.md).
0 commit comments