Skip to content

Commit 94ed314

Browse files
authored
Merge pull request #15 from stellarwp/fix/as-schedule-action-and-table-registration
Addresses table registration and scheduling issues
2 parents d93bced + 8134d06 commit 94ed314

17 files changed

+599
-51
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
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.7] 2025-09-08
6+
7+
* Fix - Ensure the regulator is registered only when the tables are created/updated successfully.
8+
* Fix - When scheduling an action, return 0 if the action ID is not an integer.
9+
* Fix - Fix fetch_all Custom_Table_Query_Methods batch generator by properly incrementing the offset.
10+
* Tweak - Update the schema version of the Tasks table to 0.0.3 to fix a typo in the version string.
11+
* Tweak - Update the get_pending_actions_by_ids method to also exclude null actions.
12+
* Tweak - Use the hook `action_scheduler_init` to determine if Action Scheduler is initialized instead of the `init` hook.
13+
14+
[0.0.7]: https://github.com/stellarwp/shepherd/releases/tag/0.0.7
15+
516
## [0.0.6] 2025-08-26
617

718
* Fix - Update Email task to properly handle multiple email recipients separated by commas.

docs/advanced-usage.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,67 @@ When dispatching a duplicate task:
9797

9898
Prevents accidental duplication and is enabled by default.
9999

100+
## Task Dispatching Requirements (Since 0.0.7)
101+
102+
When dispatching tasks, Shepherd performs several checks:
103+
104+
1. **Table Registration**: Verifies that Shepherd's database tables are registered
105+
2. **Action Scheduler**: Ensures Action Scheduler is initialized
106+
107+
### Synchronous Fallback When Tables Not Registered
108+
109+
If Shepherd's database tables are not yet registered when you dispatch a task, by default the task will be **processed immediately in a synchronous manner** instead of being queued for background processing. This ensures tasks can still execute even during early initialization phases.
110+
111+
```php
112+
// If tables are not registered, this task will run immediately
113+
shepherd()->dispatch( new My_Task() );
114+
```
115+
116+
You can monitor when this synchronous processing occurs:
117+
118+
```php
119+
$prefix = Config::get_hook_prefix();
120+
121+
add_action( "shepherd_{$prefix}_dispatched_sync", function( $task ) {
122+
error_log( 'Task processed synchronously: ' . get_class( $task ) );
123+
});
124+
```
125+
126+
#### Disabling Synchronous Fallback
127+
128+
If you prefer tasks to be skipped rather than processed synchronously when tables are unavailable or handle their scheduling yourself:
129+
130+
```php
131+
add_filter( "shepherd_{$prefix}_should_dispatch_sync_on_tables_unavailable", function( $should_dispatch, Task $task ) {
132+
// Return false to skip task processing when tables are not ready
133+
return false;
134+
}, 10, 2 );
135+
```
136+
137+
### Action Scheduler Initialization
138+
139+
If Action Scheduler is not yet initialized when you dispatch a task, Shepherd will automatically queue it and dispatch once Action Scheduler is ready via the `action_scheduler_init` hook.
140+
141+
### Handling Table Registration Errors (Since 0.0.7)
142+
143+
Your application should handle cases where Shepherd's tables fail to register by listening to the `shepherd_{prefix}_tables_error` action:
144+
145+
```php
146+
$prefix = Config::get_hook_prefix();
147+
148+
add_action( "shepherd_{$prefix}_tables_error", function( $error ) {
149+
// Log the error
150+
error_log( 'Shepherd tables failed to register: ' . $error->getMessage() );
151+
152+
// Notify administrators
153+
add_action( 'admin_notices', function() use ( $error ) {
154+
echo '<div class="notice notice-error"><p>' . esc_html__( 'Background processing is unavailable. Please contact support.', 'stellarwp-shepherd' ) . '</p></div>';
155+
} );
156+
});
157+
```
158+
159+
If this action is not handled, Shepherd will trigger a `_doing_it_wrong` notice to alert developers during development.
160+
100161
## Logging
101162

102163
Comprehensive logging tracks the complete task lifecycle.

docs/api-reference.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@ The main orchestrator for task scheduling and processing.
1818

1919
#### Methods
2020

21-
##### `dispatch( Task $task, int $delay = 0 ): void`
21+
##### `dispatch( Task $task, int $delay = 0 ): self`
2222

2323
Schedules a task for execution.
2424

2525
- **Parameters:**
2626
- `$task` - The task instance to schedule
2727
- `$delay` - Delay in seconds before execution (default: 0)
28+
- **Returns:** The Regulator instance for method chaining
2829
- **Throws:** and **Catches:** `ShepherdTaskAlreadyExistsException` if duplicate task exists
2930
- **Throws:** and **Catches:** `RuntimeException` if task fails to be scheduled or inserted into the database.
31+
- **Since 0.0.7 - Synchronous Fallback:** When Shepherd tables are not registered:
32+
- Tasks are processed immediately in a synchronous manner by default
33+
- Fires `shepherd_{prefix}_dispatched_sync` action when processing synchronously
34+
- Can be disabled via `shepherd_{prefix}_should_dispatch_sync_on_tables_unavailable` filter
35+
- **Hook Integration:** As of version 0.0.7, uses `action_scheduler_init` hook instead of `init` to ensure Action Scheduler is ready.
3036
- You can listen for those errors above, by listening to the following actions:
3137
- `shepherd_{prefix}_task_scheduling_failed`
3238
- `shepherd_{prefix}_task_already_exists`
@@ -106,6 +112,8 @@ Service provider for dependency injection and initialization.
106112

107113
Initializes Shepherd and registers all components.
108114

115+
- **Since version 0.0.7:** The Regulator is only registered after tables are successfully created/updated via the `shepherd_{prefix}_tables_registered` action.
116+
109117
##### `set_container( ContainerInterface $container ): void`
110118

111119
Sets the dependency injection container.
@@ -118,6 +126,19 @@ Returns the container instance.
118126

119127
Checks if Shepherd has been registered.
120128

129+
##### `register_regulator(): void`
130+
131+
Registers the Regulator component to start processing tasks.
132+
133+
- **Since:** 0.0.7
134+
- **Visibility:** Public
135+
- **Purpose:** Separated from the main registration flow to allow for conditional registration
136+
- **Behavior:**
137+
- Retrieves the Regulator instance from the DI container
138+
- Calls the Regulator's `register()` method to initialize task processing
139+
- **Hook:** Automatically called on `shepherd_{prefix}_tables_registered` action
140+
- **Usage:** Can be manually removed from the action hook if custom registration timing is needed
141+
121142
##### `delete_tasks_on_action_deletion( int $action_id ): void`
122143

123144
Automatically removes task data when Action Scheduler deletes an action.
@@ -133,6 +154,64 @@ Automatically removes task data when Action Scheduler deletes an action.
133154

134155
---
135156

157+
### `Action_Scheduler_Methods`
158+
159+
Wrapper class for Action Scheduler integration (since 0.0.1).
160+
161+
#### Methods
162+
163+
##### `has_scheduled_action( string $hook, array $args = [], string $group = '' ): bool`
164+
165+
Checks if an action is scheduled.
166+
167+
- **Parameters:**
168+
- `$hook` - The hook of the action
169+
- `$args` - The arguments of the action
170+
- `$group` - The group of the action
171+
- **Returns:** Whether the action is scheduled
172+
173+
##### `schedule_single_action( int $timestamp, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int`
174+
175+
Schedules a single action.
176+
177+
- **Parameters:**
178+
- `$timestamp` - The timestamp when the action should run
179+
- `$hook` - The hook of the action
180+
- `$args` - The arguments of the action
181+
- `$group` - The group of the action
182+
- `$unique` - Whether the action should be unique
183+
- `$priority` - The priority of the action (0-255)
184+
- **Returns:** The action ID, or 0 if scheduling failed (since 0.0.7)
185+
186+
##### `get_action_by_id( int $action_id ): ActionScheduler_Action`
187+
188+
Gets an action by its ID.
189+
190+
- **Parameters:**
191+
- `$action_id` - The action ID
192+
- **Returns:** The action object
193+
- **Throws:** `RuntimeException` if the action is not found
194+
195+
##### `get_actions_by_ids( array $action_ids ): array`
196+
197+
Gets multiple actions by their IDs.
198+
199+
- **Parameters:**
200+
- `$action_ids` - Array of action IDs
201+
- **Returns:** Array of ActionScheduler_Action objects keyed by ID
202+
- **Throws:** `RuntimeException` if any action is not found
203+
204+
##### `get_pending_actions_by_ids( array $action_ids ): array`
205+
206+
Gets pending actions by their IDs, excluding finished and null actions.
207+
208+
- **Parameters:**
209+
- `$action_ids` - Array of action IDs
210+
- **Returns:** Array of pending ActionScheduler_Action objects
211+
- **Since 0.0.7:** Also excludes `ActionScheduler_NullAction` instances
212+
213+
---
214+
136215
### `Email` Task
137216

138217
Built-in task for sending emails asynchronously.
@@ -416,6 +495,13 @@ Table name: `shepherd_{prefix}_task_logs`
416495

417496
### Actions
418497

498+
- `shepherd_{prefix}_tables_registered` - Fired when Shepherd tables are successfully registered (since 0.0.7)
499+
- No parameters
500+
- Used internally to ensure safe initialization of the Regulator
501+
502+
- `shepherd_{prefix}_tables_error` - Fired when database table creation/update fails (since 0.0.7)
503+
- Parameters: `$exception` (DatabaseQueryException)
504+
419505
- `shepherd_{prefix}_task_scheduling_failed` - Fired when a task fails to be scheduled
420506
- Parameters: `$task`, `$exception`
421507

@@ -442,4 +528,6 @@ Table name: `shepherd_{prefix}_task_logs`
442528

443529
### Filters
444530

445-
Currently, Shepherd does not provide any filters.
531+
- `shepherd_{prefix}_should_log` - Filter to control whether logging should occur (since 0.0.5)
532+
- Parameters: `$should_log` (bool, default: true)
533+
- Return false to disable logging

docs/configuration.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,22 @@ The tasks table is created automatically when you call `Provider::register()`.
115115

116116
The logs table is only created if you're using the `DB_Logger`. When using the default `ActionScheduler_DB_Logger`, logs are stored in Action Scheduler's existing `actionscheduler_logs` table.
117117

118+
**Since version 0.0.7:**
119+
120+
- Tables are created/updated before the Regulator is initialized
121+
- The `shepherd_{prefix}_tables_registered` action is fired upon successful table registration
122+
- The `shepherd_{prefix}_tables_error` action is fired if table creation fails
123+
- The Regulator will only be registered after tables are successfully created
124+
118125
## Action Scheduler Configuration
119126

120127
Shepherd uses Action Scheduler for task scheduling. You can configure Action Scheduler settings separately:
121128

129+
**Since version 0.0.7:**
130+
131+
- Shepherd now uses the `action_scheduler_init` hook to ensure Action Scheduler is ready before dispatching tasks
132+
- Tasks dispatched before Action Scheduler initialization are automatically queued and dispatched once it's ready
133+
122134
### Custom Action Scheduler Tables
123135

124136
Action Scheduler uses its own tables. If you need custom table names, configure Action Scheduler before loading Shepherd.
@@ -161,6 +173,7 @@ $container->get( Provider::class )->register();
161173
2. **Use Consistent Prefixes**: Keep your hook prefix consistent across your application
162174
3. **Container Singleton**: Always register Provider as a singleton
163175
4. **Check Registration**: If you are not sure whether Shepherd is registered, you can check it using `Provider::is_registered()` before accessing Shepherd
176+
5. **Table Initialization** (since 0.0.7): Listen to `shepherd_{prefix}_tables_registered` if you need to perform actions after tables are ready
164177

165178
```php
166179
if ( ! Provider::is_registered() ) {

docs/getting-started.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ shepherd()->dispatch( $my_task, 5 * MINUTE_IN_SECONDS ); // Execute after 5 minu
138138

139139
### What Happens Next?
140140

141-
1. Shepherd schedules your task with Action Scheduler
142-
2. WordPress cron picks up the task
143-
3. Your task's `process()` method executes
144-
4. The lifecycle is logged in the database
145-
5. Failed tasks may be retried based on configuration
141+
1. Shepherd validates that its tables are registered (since 0.0.7)
142+
2. Shepherd schedules your task with Action Scheduler
143+
3. WordPress cron picks up the task
144+
4. Your task's `process()` method executes
145+
5. The lifecycle is logged in the database
146+
6. Failed tasks may be retried based on configuration
146147

147148
Check `debug.log` for the message "Shepherd Task: Hello, World! with code 200".
148149

@@ -176,3 +177,5 @@ If your tasks aren't running:
176177
2. **Verify WP-Cron**: Ensure WordPress cron is running or set up a real cron job
177178
3. **Check Logs**: Look for errors in your WordPress debug log
178179
4. **Database Tables**: Ensure Shepherd's tables were created during registration
180+
5. **Table Registration** (since 0.0.7): Check for `shepherd_{prefix}_tables_error` action if tables fail to create
181+
6. **Initialization Order** (since 0.0.7): Ensure Action Scheduler is loaded before dispatching tasks

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.6
12+
* Version: 0.0.7
1313
* Author: StellarWP
1414
* Author URI: https://stellarwp.com
1515
* License: GPL-2.0-or-later

src/Action_Scheduler_Methods.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use ActionScheduler;
1515
use ActionScheduler_Action;
1616
use ActionScheduler_FinishedAction;
17+
use ActionScheduler_NullAction;
1718
use RuntimeException;
1819

1920
/**
@@ -43,6 +44,7 @@ public static function has_scheduled_action( string $hook, array $args = [], str
4344
* Schedules a single action.
4445
*
4546
* @since 0.0.1
47+
* @since 0.0.7 Updated to return 0 if the action ID is not an integer.
4648
*
4749
* @param int $timestamp The timestamp of the action.
4850
* @param string $hook The hook of the action.
@@ -54,7 +56,9 @@ public static function has_scheduled_action( string $hook, array $args = [], str
5456
* @return int The action ID.
5557
*/
5658
public static function schedule_single_action( int $timestamp, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
57-
return as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority );
59+
$action_id = as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority );
60+
61+
return is_int( $action_id ) ? $action_id : 0;
5862
}
5963

6064
/**
@@ -112,6 +116,7 @@ public static function get_actions_by_ids( array $action_ids ): array {
112116
* Gets pending actions by their IDs.
113117
*
114118
* @since 0.0.1
119+
* @since 0.0.7 Updated to filter out null actions.
115120
*
116121
* @param array $action_ids The action IDs.
117122
*
@@ -120,6 +125,6 @@ public static function get_actions_by_ids( array $action_ids ): array {
120125
public static function get_pending_actions_by_ids( array $action_ids ): array {
121126
$actions = self::get_actions_by_ids( $action_ids );
122127

123-
return array_filter( $actions, fn( ActionScheduler_Action $action ) => ! $action instanceof ActionScheduler_FinishedAction );
128+
return array_filter( $actions, static fn( ActionScheduler_Action $action ) => ! $action instanceof ActionScheduler_FinishedAction && ! $action instanceof ActionScheduler_NullAction );
124129
}
125130
}

src/Provider.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class Provider extends Provider_Abstract {
5959
* Registers Shepherd's specific providers and starts core functionality
6060
*
6161
* @since 0.0.1
62+
* @since 0.0.7 Updated to register the regulator after the tables are registered successfully.
6263
*
6364
* @return void The method does not return any value.
6465
*/
@@ -78,19 +79,38 @@ public function register(): void {
7879
$this->container->singleton( Logger::class, Config::get_logger() );
7980
$this->container->singleton( Tables_Provider::class );
8081
$this->container->singleton( Regulator::class );
82+
83+
$prefix = Config::get_hook_prefix();
84+
85+
add_action( "shepherd_{$prefix}_tables_registered", [ $this, 'register_regulator' ] );
86+
87+
if ( ! has_action( "shepherd_{$prefix}_tables_error" ) ) {
88+
_doing_it_wrong( __METHOD__, esc_html__( 'Your software should be handling the case where Shepherd tables are not registered successfully and notify your end users about it.', 'stellarwp-shepherd' ), '0.0.7' );
89+
}
90+
8191
$this->container->get( Tables_Provider::class )->register();
82-
$this->container->get( Regulator::class )->register();
8392

8493
add_action( 'action_scheduler_deleted_action', [ $this, 'delete_tasks_on_action_deletion' ] );
8594

8695
self::$has_registered = true;
8796
}
8897

98+
/**
99+
* Registers the regulator.
100+
*
101+
* @since 0.0.7
102+
*
103+
* @return void
104+
*/
105+
public function register_regulator(): void {
106+
$this->container->get( Regulator::class )->register();
107+
}
108+
89109
/**
90110
* Requires Action Scheduler.
91111
*
92112
* @since 0.0.1
93-
* @since 0.0.2
113+
* @since 0.0.2 Look into multiple places for the action scheduler main file.
94114
*
95115
* @return void
96116
*

0 commit comments

Comments
 (0)