Skip to content

Commit 0af4cfc

Browse files
authored
Merge pull request #18 from stellarwp/feat/introduce-the-ability-overwrite-the-handler-via-a-filter
Introduces the ability to have custom handlers via filter
2 parents 6ff5c2c + c63ce4b commit 0af4cfc

File tree

5 files changed

+167
-4
lines changed

5 files changed

+167
-4
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file. This project adhere to the [Semantic Versioning](http://semver.org/) standard.
44

5+
## [0.0.9] 2025-11-17
6+
7+
* Tweak - Add a filter `shepherd_{prefix}_dispatch_handler` to allow for custom dispatch handlers.
8+
9+
[0.0.9]: https://github.com/stellarwp/shepherd/releases/tag/0.0.9
10+
511
## [0.0.8] 2025-10-02
612

713
* Feature - Update the stellarwp/schema library to v3.

docs/advanced-usage.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,14 @@ add_filter( "shepherd_{$prefix}_should_dispatch_sync_on_tables_unavailable", fun
138138
// Default behavior (since 0.0.8):
139139
// - Returns true when delay is 0 (immediate execution)
140140
// - Returns false when delay > 0 (skip execution)
141-
141+
142142
// Override examples:
143143
// Always process synchronously regardless of delay
144144
return true;
145-
145+
146146
// Never process synchronously
147147
// return false;
148-
148+
149149
// Custom logic based on task type
150150
// return $task instanceof Critical_Task;
151151
}, 10, 2 );
@@ -371,6 +371,31 @@ The task tables include indexes on:
371371
- **Indexed Queries**: All cleanup queries use indexed columns for optimal performance
372372
- **Minimal Overhead**: Action deletion hooks add minimal overhead to Action Scheduler operations
373373

374+
## Custom Dispatch Handlers
375+
376+
**Since 0.0.9**, you can completely override Shepherd's default dispatch behavior by providing a custom handler via a filter. This is useful for advanced scenarios where you need full control over how tasks are dispatched.
377+
378+
### Basic Usage
379+
380+
```php
381+
$prefix = Config::get_hook_prefix();
382+
383+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) {
384+
// Return a callable that will handle the dispatch
385+
return function( $task, $delay ) {
386+
// Your custom dispatch logic
387+
my_custom_task_queue()->add( $task, $delay );
388+
};
389+
}, 10, 3 );
390+
```
391+
392+
### Important Notes
393+
394+
- **Return null to use default handler**: If you return `null` or a non-callable value, Shepherd will use its default dispatch logic
395+
- **Handler signature**: Your custom handler must accept two parameters: `$task` (Task instance) and `$delay` (integer)
396+
- **Complete override**: When you provide a custom handler, Shepherd's default dispatch logic (including Action Scheduler integration) is completely bypassed
397+
- **Responsibility**: Your custom handler is responsible for all aspects of task execution, including scheduling, retries, and logging
398+
374399
## Advanced Integration
375400

376401
### WordPress Hooks

shepherd.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @wordpress-plugin
1010
* Plugin Name: Shepherd
1111
* Description: A library for offloading tasks to background processes.
12-
* Version: 0.0.8
12+
* Version: 0.0.9
1313
* Author: StellarWP
1414
* Author URI: https://stellarwp.com
1515
* License: GPL-2.0-or-later

src/Regulator.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public function untrack_action(): void {
143143
* @since 0.0.7 Updated to check if the Shepherd tables have been registered already.
144144
* @since 0.0.7 Updated to use the `action_scheduler_init` hook instead of the `init` hook to check if Action Scheduler is initialized.
145145
* @since 0.0.8 Updated to use the delay to determine if the task should be dispatched synchronously.
146+
* @since 0.0.9 Added a filter `shepherd_{prefix}_dispatch_handler` to allow for custom dispatch handlers.
146147
*
147148
* @param Task $task The task to dispatch.
148149
* @param int $delay The delay in seconds before the task is processed.
@@ -152,6 +153,29 @@ public function untrack_action(): void {
152153
public function dispatch( Task $task, int $delay = 0 ): self {
153154
$prefix = Config::get_hook_prefix();
154155

156+
/**
157+
* Filters the dispatch handler.
158+
*
159+
* @since TBD
160+
*
161+
* @param callable|null $handler The dispatch handler.
162+
* @param Task $task The task to dispatch.
163+
* @param int $delay The delay in seconds before the task is processed.
164+
*/
165+
$handler = apply_filters( "shepherd_{$prefix}_dispatch_handler", null, $task, $delay );
166+
if ( null !== $handler && is_callable( $handler ) ) {
167+
try {
168+
$handler( $task, $delay );
169+
} catch ( Exception $e ) {
170+
/**
171+
* Documented in the dispatch_callback method.
172+
*/
173+
do_action( 'shepherd_' . Config::get_hook_prefix() . '_task_scheduling_failed', $task, new RuntimeException( $e->getMessage(), $e->getCode(), $e ) );
174+
}
175+
176+
return $this;
177+
}
178+
155179
if ( ! did_action( "shepherd_{$prefix}_tables_registered" ) ) {
156180
/**
157181
* Filters whether to dispatch a task synchronously.

tests/wpunit/Regulator_Test.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use StellarWP\Shepherd\Tests\Tasks\Do_Action_Task;
1111
use StellarWP\Shepherd\Tests\Tasks\Do_Prefixed_Action_Task;
1212
use StellarWP\Shepherd\Tests\Traits\With_AS_Assertions;
13+
use Exception;
1314

1415
class Regulator_Test extends WPTestCase {
1516
use With_Uopz;
@@ -219,4 +220,111 @@ public function it_should_schedule_cleanup_task_when_tables_are_registered(): vo
219220

220221
$this->assertEquals( $current_count + 1, did_action( 'shepherd_' . $prefix . '_cleanup_task_scheduled' ), 'Cleanup task should not be scheduled when tables are not registered' );
221222
}
223+
224+
/**
225+
* @test
226+
*/
227+
public function it_should_use_custom_dispatch_handler_when_provided_via_filter(): void {
228+
$prefix = Config::get_hook_prefix();
229+
$regulator = Config::get_container()->get( Regulator::class );
230+
231+
$custom_handler_called = false;
232+
$handler_received_task = null;
233+
$handler_received_delay = null;
234+
235+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) use ( &$custom_handler_called, &$handler_received_task, &$handler_received_delay ) {
236+
return function( $task, $delay ) use ( &$custom_handler_called, &$handler_received_task, &$handler_received_delay ) {
237+
$custom_handler_called = true;
238+
$handler_received_task = $task;
239+
$handler_received_delay = $delay;
240+
};
241+
}, 10, 3 );
242+
243+
$test_task = new Do_Action_Task();
244+
$delay = 60;
245+
246+
$last_task_id = $regulator->get_last_scheduled_task_id();
247+
248+
$regulator->dispatch( $test_task, $delay );
249+
250+
$this->assertTrue( $custom_handler_called, 'Custom dispatch handler should have been called' );
251+
$this->assertSame( $test_task, $handler_received_task, 'Custom handler should receive the task' );
252+
$this->assertSame( $delay, $handler_received_delay, 'Custom handler should receive the delay' );
253+
254+
$this->assertSame( $last_task_id, $regulator->get_last_scheduled_task_id(), 'Task should not be scheduled when custom handler is used' );
255+
}
256+
257+
/**
258+
* @test
259+
*/
260+
public function it_should_fire_fail_action_when_throwing_exception_in_custom_dispatch_handler(): void {
261+
$prefix = Config::get_hook_prefix();
262+
$regulator = Config::get_container()->get( Regulator::class );
263+
264+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) {
265+
return function( $task, $delay ) {
266+
throw new Exception( 'Custom dispatch handler failed' );
267+
};
268+
}, 10, 3 );
269+
270+
$test_task = new Do_Action_Task();
271+
$delay = 60;
272+
273+
$this->assertSame( 0, did_action( "shepherd_{$prefix}_task_scheduling_failed" ) );
274+
$regulator->dispatch( $test_task, $delay );
275+
$this->assertTrue( 0 < did_action( "shepherd_{$prefix}_task_scheduling_failed" ) );
276+
}
277+
278+
/**
279+
* @test
280+
*/
281+
public function it_should_do_default_when_returning_null(): void {
282+
$prefix = Config::get_hook_prefix();
283+
$regulator = Config::get_container()->get( Regulator::class );
284+
285+
$called = false;
286+
287+
// Add a custom dispatch handler via filter
288+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) use ( &$called ) {
289+
return function( $task, $delay ) use ( &$called ) {
290+
$called = true;
291+
};
292+
}, 10, 3 );
293+
294+
// Add a custom dispatch handler via filter
295+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) {
296+
return null;
297+
}, 10, 3 );
298+
299+
$test_task = new Do_Action_Task();
300+
$delay = 60;
301+
302+
$last_task_id = $regulator->get_last_scheduled_task_id();
303+
304+
$regulator->dispatch( $test_task, $delay );
305+
306+
$this->assertFalse( $called, 'Custom dispatch handler should have been called' );
307+
$this->assertNotSame( $last_task_id, $regulator->get_last_scheduled_task_id(), 'Task should be scheduled when custom handler is not used' );
308+
}
309+
310+
/**
311+
* @test
312+
*/
313+
public function it_should_do_default_when_returning_non_callable(): void {
314+
$prefix = Config::get_hook_prefix();
315+
$regulator = Config::get_container()->get( Regulator::class );
316+
317+
318+
// Add a custom dispatch handler via filter
319+
add_filter( "shepherd_{$prefix}_dispatch_handler", function( $handler, $task, $delay ) {
320+
return 'not a callable';
321+
}, 10, 3 );
322+
323+
$test_task = new Do_Action_Task();
324+
$delay = 60;
325+
326+
$regulator->dispatch( $test_task, $delay );
327+
328+
$this->assertNotNull( $regulator->get_last_scheduled_task_id(), 'Task should be scheduled when custom handler is not callable' );
329+
}
222330
}

0 commit comments

Comments
 (0)