Skip to content

Commit 269a934

Browse files
committed
feat: add queue task type to scheduler
1 parent f75ca76 commit 269a934

File tree

10 files changed

+166
-3
lines changed

10 files changed

+166
-3
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"require": {
2121
"php": "^8.1",
2222
"ext-json": "*",
23-
"codeigniter4/settings": "^2.0"
23+
"codeigniter4/settings": "^2.0",
24+
"codeigniter4/queue": "dev-develop"
2425
},
2526
"require-dev": {
2627
"codeigniter4/devkit": "^1.3",

docs/basic-usage.md

+42
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,48 @@ a simple URL string, you can use a closure or command instead.
8181
$schedule->url('https://my-status-cloud.com?site=foo.com')->everyFiveMinutes();
8282
```
8383

84+
### Scheduling Queue Jobs
85+
86+
If you want to schedule a Queue Job, you can use the `queue()` method and specify the queue name, job name and data your job needs:
87+
88+
```php
89+
$schedule->queue('queue-name', 'jobName', ['data' => 'array'])->hourly();
90+
```
91+
92+
!!! note
93+
94+
To learn more about the [Queue package](https://github.com/codeigniter4/queue) you can visit a project page.
95+
96+
97+
The `singleInstance()` option, described in the next section, works a bit differently than with other scheduling methods.
98+
Since queue jobs are added quickly and processed later in the background, the lock is applied as soon as the job is queued - not when it actually runs.
99+
100+
```php
101+
$schedule->queue('queue-name', 'jobName', ['data' => 'array'])
102+
->hourly()
103+
->singleInstance();
104+
```
105+
106+
This means:
107+
108+
- The lock is created immediately when the job is queued.
109+
- The lock is released only after the job is processed (whether it succeeds or fails).
110+
111+
We can optionally pass a TTL to `singleInstance()` to limit how long the job lock should last:
112+
113+
```php
114+
$schedule->queue('queue-name', 'jobName', ['data' => 'array'])
115+
->hourly()
116+
->singleInstance(30 * MINUTE);
117+
```
118+
119+
How it works:
120+
121+
- The lock is set immediately when the job is queued.
122+
- The job must start processing before the TTL expires (in this case, within 30 minutes).
123+
- Once the job starts, the lock is renewed for the same TTL.
124+
- So, effectively, you have 30 minutes to start, and another 30 minutes to complete the job.
125+
84126
## Single Instance Tasks
85127

86128
Some tasks can run longer than their scheduled interval. To prevent multiple instances of the same task running simultaneously, you can use the `singleInstance()` method:

phpstan-baseline.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ parameters:
3939
-
4040
message: '#^Call to method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''CodeIgniter\\\\Tasks\\\\Task'' and CodeIgniter\\Tasks\\Task will always evaluate to true\.$#'
4141
identifier: method.alreadyNarrowedType
42-
count: 3
42+
count: 4
4343
path: tests/unit/SchedulerTest.php
4444

4545
-

src/Scheduler.php

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace CodeIgniter\Tasks;
1515

1616
use Closure;
17+
use CodeIgniter\Queue\Queue;
18+
use CodeIgniter\Tasks\Exceptions\TasksException;
1719

1820
class Scheduler
1921
{
@@ -73,6 +75,16 @@ public function url(string $url): Task
7375
return $this->createTask('url', $url);
7476
}
7577

78+
/**
79+
* Schedule a queue job.
80+
*
81+
* @throws TasksException
82+
*/
83+
public function queue(string $queue, string $job, array $data): Task
84+
{
85+
return $this->createTask('queue', [$queue, $job, $data]);
86+
}
87+
7688
// --------------------------------------------------------------------
7789

7890
/**

src/Task.php

+22-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use CodeIgniter\Events\Events;
1717
use CodeIgniter\I18n\Time;
18+
use CodeIgniter\Queue\Payloads\PayloadMetadata;
1819
use CodeIgniter\Tasks\Exceptions\TasksException;
1920
use InvalidArgumentException;
2021
use ReflectionException;
@@ -48,6 +49,7 @@ class Task
4849
'closure',
4950
'event',
5051
'url',
52+
'queue',
5153
];
5254

5355
/**
@@ -142,7 +144,7 @@ public function run()
142144

143145
return $this->{$method}();
144146
} finally {
145-
if ($this->singleInstance) {
147+
if ($this->singleInstance && $this->getType() !== 'queue') {
146148
cache()->delete($lockKey);
147149
}
148150
}
@@ -297,6 +299,25 @@ protected function runUrl()
297299
return $response->getBody();
298300
}
299301

302+
/**
303+
* Sends a job to the queue.
304+
*/
305+
protected function runQueue()
306+
{
307+
$queueAction = $this->getAction();
308+
309+
if ($this->singleInstance) {
310+
// Create PayloadMetadata instance with the task lock key
311+
$queueAction[] = new PayloadMetadata([
312+
'queue' => $queueAction[0],
313+
'taskLockTTL' => $this->singleInstanceTTL,
314+
'taskLockKey' => $this->getLockKey(),
315+
]);
316+
}
317+
318+
return service('queue')->push(...$queueAction);
319+
}
320+
300321
/**
301322
* Builds a unique name for the task.
302323
* Used when an existing name doesn't exist.

src/Test/MockTask.php

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function run()
4646
'closure' => 42,
4747
'event' => true,
4848
'url' => 'body',
49+
'queue' => true,
4950
][$this->type];
5051
}
5152
}

tests/_support/Config/Registrar.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter Tasks.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Tests\Support\Config;
15+
16+
class Registrar
17+
{
18+
public static function Queue(): array
19+
{
20+
return [
21+
'jobHandlers' => [
22+
'job-example' => 'Tests\Jobs\Example',
23+
],
24+
];
25+
}
26+
}

tests/_support/Jobs/Example.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter Tasks.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Tests\Support\Jobs;
15+
16+
use CodeIgniter\Queue\BaseJob;
17+
use CodeIgniter\Queue\Interfaces\JobInterface;
18+
19+
class Example extends BaseJob implements JobInterface
20+
{
21+
public function process(): bool
22+
{
23+
return true;
24+
}
25+
}

tests/unit/SchedulerTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,12 @@ public function testShellSavesTask()
5656
$this->assertInstanceOf(Task::class, $task);
5757
$this->assertSame('foo:bar', $task->getAction());
5858
}
59+
60+
public function testQueueSavesTask()
61+
{
62+
$task = $this->scheduler->queue('example', 'job-example', ['data' => 'array']);
63+
64+
$this->assertInstanceOf(Task::class, $task);
65+
$this->assertSame(['example', 'job-example', ['data' => 'array']], $task->getAction());
66+
}
5967
}

tests/unit/TaskTest.php

+27
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,31 @@ public function testSingleInstanceWithCustomTTL()
297297

298298
$this->assertNull($this->getPrivateProperty($task2, 'singleInstanceTTL'));
299299
}
300+
301+
public function testRunQueue()
302+
{
303+
$task = new Task('queue', ['example', 'job-example', []]);
304+
$task->named('test_run_queue');
305+
306+
$result = $task->run();
307+
$this->assertTrue($result);
308+
309+
// No lock
310+
$lockKey = $this->getPrivateMethodInvoker($task, 'getLockKey')();
311+
$this->assertNull(cache()->get($lockKey));
312+
}
313+
314+
public function testRunQueueWithSingleInstance()
315+
{
316+
$task = new Task('queue', ['example', 'job-example', []]);
317+
$task->named('test_run_queue_single');
318+
$task->singleInstance();
319+
320+
$result = $task->run();
321+
$this->assertTrue($result);
322+
323+
// Lock is still present
324+
$lockKey = $this->getPrivateMethodInvoker($task, 'getLockKey')();
325+
$this->assertNotNull(cache()->get($lockKey));
326+
}
300327
}

0 commit comments

Comments
 (0)