Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2a02f2c
Ensure we always return int while scheduling an action
dpanta94 Sep 1, 2025
f388c98
Fix typo of version string
dpanta94 Sep 1, 2025
9a4424f
Ensure that shepherd tables are up before using shepherd. Shepherd ta…
dpanta94 Sep 1, 2025
44c11ed
Update docs
dpanta94 Sep 1, 2025
c07e4a0
Fix phpcs
dpanta94 Sep 1, 2025
0dfe133
remove not needed param doc comment
dpanta94 Sep 1, 2025
643d6be
Use more suitable hooks
dpanta94 Sep 3, 2025
aa3a4e0
Exclude ActionScheduler_NullAction from pending actions
dpanta94 Sep 3, 2025
1c3a8b0
Updated docs
dpanta94 Sep 3, 2025
f8264c4
Updated docs
dpanta94 Sep 3, 2025
eaf839a
Fixed changelog
dpanta94 Sep 3, 2025
3483158
Patch offset generator
dpanta94 Sep 4, 2025
159e8ae
Updated changelog
dpanta94 Sep 4, 2025
1048bf3
Updated docs
dpanta94 Sep 4, 2025
b69be6b
Fix offset placement in query
dpanta94 Sep 4, 2025
2756df1
make register_regulator an anonymous function
dpanta94 Sep 7, 2025
0c027ab
Merge 2756df1132fd7f2214dc403cd0732a7d9110275d into d93bcedf3a28fee87…
dpanta94 Sep 7, 2025
8309ce7
chore: autopublish 2025-09-07T13:02:08Z
github-actions[bot] Sep 7, 2025
74d9651
Update doc block
dpanta94 Sep 7, 2025
4c434d9
Merge remote-tracking branch 'origin/fix/as-schedule-action-and-table…
dpanta94 Sep 7, 2025
067f9c5
Updated docblocks
dpanta94 Sep 7, 2025
0ca8cab
Fix tests
dpanta94 Sep 7, 2025
b93c555
added test coverage
dpanta94 Sep 8, 2025
96c39a0
Update CHANGELOG.md
dpanta94 Sep 8, 2025
d160818
Update src/Action_Scheduler_Methods.php
dpanta94 Sep 8, 2025
c282fb2
Allow removal of register_regulator action
dpanta94 Sep 8, 2025
46b88ab
Instead of doing_it_wrong, process the task syncronous when tables ar…
dpanta94 Sep 8, 2025
a3d4b1e
Notify library users that they should listen for table failure
dpanta94 Sep 8, 2025
8936385
Test coverage for triggering doing_it_wrong
dpanta94 Sep 8, 2025
e194358
Updated docs
dpanta94 Sep 8, 2025
7ca5617
Test coverage for sync task dispatch when tables unavailable
dpanta94 Sep 8, 2025
f5c7e97
Ensure provider is not registering multiple times with the same conta…
dpanta94 Sep 8, 2025
ffe8e1b
Reduced mocking
dpanta94 Sep 8, 2025
8134d06
added one more assertion
dpanta94 Sep 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

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

## [0.0.7] 2025-09-08

* Fix - Ensure the regulator is registered only when the tables are created/updated successfully.
* Fix - When scheduling an action, return 0 if the action ID is not an integer.
* Fix - Fix fetch_all Custom_Table_Query_Methods batch generator by properly incrementing the offset.
* Tweak - Update the schema version of the Tasks table to 0.0.3 to fix a typo in the version string.
* Tweak - Update the get_pending_actions_by_ids method to also exclude null actions.
* Tweak - Use the hook `action_scheduler_init` to determine if Action Scheduler is initialized instead of the `init` hook.

[0.0.7]: https://github.com/stellarwp/shepherd/releases/tag/0.0.7

## [0.0.6] 2025-08-26

* Fix - Update Email task to properly handle multiple email recipients separated by commas.
Expand Down
61 changes: 61 additions & 0 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,67 @@ When dispatching a duplicate task:

Prevents accidental duplication and is enabled by default.

## Task Dispatching Requirements (Since 0.0.7)

When dispatching tasks, Shepherd performs several checks:

1. **Table Registration**: Verifies that Shepherd's database tables are registered
2. **Action Scheduler**: Ensures Action Scheduler is initialized

### Synchronous Fallback When Tables Not Registered

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.

```php
// If tables are not registered, this task will run immediately
shepherd()->dispatch( new My_Task() );
```

You can monitor when this synchronous processing occurs:

```php
$prefix = Config::get_hook_prefix();

add_action( "shepherd_{$prefix}_dispatched_sync", function( $task ) {
error_log( 'Task processed synchronously: ' . get_class( $task ) );
});
```

#### Disabling Synchronous Fallback

If you prefer tasks to be skipped rather than processed synchronously when tables are unavailable or handle their scheduling yourself:

```php
add_filter( "shepherd_{$prefix}_should_dispatch_sync_on_tables_unavailable", function( $should_dispatch, Task $task ) {
// Return false to skip task processing when tables are not ready
return false;
}, 10, 2 );
```

### Action Scheduler Initialization

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.

### Handling Table Registration Errors (Since 0.0.7)

Your application should handle cases where Shepherd's tables fail to register by listening to the `shepherd_{prefix}_tables_error` action:

```php
$prefix = Config::get_hook_prefix();

add_action( "shepherd_{$prefix}_tables_error", function( $error ) {
// Log the error
error_log( 'Shepherd tables failed to register: ' . $error->getMessage() );

// Notify administrators
add_action( 'admin_notices', function() use ( $error ) {
echo '<div class="notice notice-error"><p>' . esc_html__( 'Background processing is unavailable. Please contact support.', 'stellarwp-shepherd' ) . '</p></div>';
} );
});
```

If this action is not handled, Shepherd will trigger a `_doing_it_wrong` notice to alert developers during development.

## Logging

Comprehensive logging tracks the complete task lifecycle.
Expand Down
92 changes: 90 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ The main orchestrator for task scheduling and processing.

#### Methods

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

Schedules a task for execution.

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

Initializes Shepherd and registers all components.

- **Since version 0.0.7:** The Regulator is only registered after tables are successfully created/updated via the `shepherd_{prefix}_tables_registered` action.

##### `set_container( ContainerInterface $container ): void`

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

Checks if Shepherd has been registered.

##### `register_regulator(): void`

Registers the Regulator component to start processing tasks.

- **Since:** 0.0.7
- **Visibility:** Public
- **Purpose:** Separated from the main registration flow to allow for conditional registration
- **Behavior:**
- Retrieves the Regulator instance from the DI container
- Calls the Regulator's `register()` method to initialize task processing
- **Hook:** Automatically called on `shepherd_{prefix}_tables_registered` action
- **Usage:** Can be manually removed from the action hook if custom registration timing is needed

##### `delete_tasks_on_action_deletion( int $action_id ): void`

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

---

### `Action_Scheduler_Methods`

Wrapper class for Action Scheduler integration (since 0.0.1).

#### Methods

##### `has_scheduled_action( string $hook, array $args = [], string $group = '' ): bool`

Checks if an action is scheduled.

- **Parameters:**
- `$hook` - The hook of the action
- `$args` - The arguments of the action
- `$group` - The group of the action
- **Returns:** Whether the action is scheduled

##### `schedule_single_action( int $timestamp, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int`

Schedules a single action.

- **Parameters:**
- `$timestamp` - The timestamp when the action should run
- `$hook` - The hook of the action
- `$args` - The arguments of the action
- `$group` - The group of the action
- `$unique` - Whether the action should be unique
- `$priority` - The priority of the action (0-255)
- **Returns:** The action ID, or 0 if scheduling failed (since 0.0.7)

##### `get_action_by_id( int $action_id ): ActionScheduler_Action`

Gets an action by its ID.

- **Parameters:**
- `$action_id` - The action ID
- **Returns:** The action object
- **Throws:** `RuntimeException` if the action is not found

##### `get_actions_by_ids( array $action_ids ): array`

Gets multiple actions by their IDs.

- **Parameters:**
- `$action_ids` - Array of action IDs
- **Returns:** Array of ActionScheduler_Action objects keyed by ID
- **Throws:** `RuntimeException` if any action is not found

##### `get_pending_actions_by_ids( array $action_ids ): array`

Gets pending actions by their IDs, excluding finished and null actions.

- **Parameters:**
- `$action_ids` - Array of action IDs
- **Returns:** Array of pending ActionScheduler_Action objects
- **Since 0.0.7:** Also excludes `ActionScheduler_NullAction` instances

---

### `Email` Task

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

### Actions

- `shepherd_{prefix}_tables_registered` - Fired when Shepherd tables are successfully registered (since 0.0.7)
- No parameters
- Used internally to ensure safe initialization of the Regulator

- `shepherd_{prefix}_tables_error` - Fired when database table creation/update fails (since 0.0.7)
- Parameters: `$exception` (DatabaseQueryException)

- `shepherd_{prefix}_task_scheduling_failed` - Fired when a task fails to be scheduled
- Parameters: `$task`, `$exception`

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

### Filters

Currently, Shepherd does not provide any filters.
- `shepherd_{prefix}_should_log` - Filter to control whether logging should occur (since 0.0.5)
- Parameters: `$should_log` (bool, default: true)
- Return false to disable logging
13 changes: 13 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,22 @@ The tasks table is created automatically when you call `Provider::register()`.

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.

**Since version 0.0.7:**

- Tables are created/updated before the Regulator is initialized
- The `shepherd_{prefix}_tables_registered` action is fired upon successful table registration
- The `shepherd_{prefix}_tables_error` action is fired if table creation fails
- The Regulator will only be registered after tables are successfully created

## Action Scheduler Configuration

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

**Since version 0.0.7:**

- Shepherd now uses the `action_scheduler_init` hook to ensure Action Scheduler is ready before dispatching tasks
- Tasks dispatched before Action Scheduler initialization are automatically queued and dispatched once it's ready

### Custom Action Scheduler Tables

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

```php
if ( ! Provider::is_registered() ) {
Expand Down
13 changes: 8 additions & 5 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,12 @@ shepherd()->dispatch( $my_task, 5 * MINUTE_IN_SECONDS ); // Execute after 5 minu

### What Happens Next?

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

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

Expand Down Expand Up @@ -176,3 +177,5 @@ If your tasks aren't running:
2. **Verify WP-Cron**: Ensure WordPress cron is running or set up a real cron job
3. **Check Logs**: Look for errors in your WordPress debug log
4. **Database Tables**: Ensure Shepherd's tables were created during registration
5. **Table Registration** (since 0.0.7): Check for `shepherd_{prefix}_tables_error` action if tables fail to create
6. **Initialization Order** (since 0.0.7): Ensure Action Scheduler is loaded before dispatching tasks
2 changes: 1 addition & 1 deletion shepherd.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @wordpress-plugin
* Plugin Name: Shepherd
* Description: A library for offloading tasks to background processes.
* Version: 0.0.6
* Version: 0.0.7
* Author: StellarWP
* Author URI: https://stellarwp.com
* License: GPL-2.0-or-later
Expand Down
9 changes: 7 additions & 2 deletions src/Action_Scheduler_Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use ActionScheduler;
use ActionScheduler_Action;
use ActionScheduler_FinishedAction;
use ActionScheduler_NullAction;
use RuntimeException;

/**
Expand Down Expand Up @@ -43,6 +44,7 @@ public static function has_scheduled_action( string $hook, array $args = [], str
* Schedules a single action.
*
* @since 0.0.1
* @since 0.0.7 Updated to return 0 if the action ID is not an integer.
*
* @param int $timestamp The timestamp of the action.
* @param string $hook The hook of the action.
Expand All @@ -54,7 +56,9 @@ public static function has_scheduled_action( string $hook, array $args = [], str
* @return int The action ID.
*/
public static function schedule_single_action( int $timestamp, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
return as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority );
$action_id = as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority );

return is_int( $action_id ) ? $action_id : 0;
}

/**
Expand Down Expand Up @@ -112,6 +116,7 @@ public static function get_actions_by_ids( array $action_ids ): array {
* Gets pending actions by their IDs.
*
* @since 0.0.1
* @since 0.0.7 Updated to filter out null actions.
*
* @param array $action_ids The action IDs.
*
Expand All @@ -120,6 +125,6 @@ public static function get_actions_by_ids( array $action_ids ): array {
public static function get_pending_actions_by_ids( array $action_ids ): array {
$actions = self::get_actions_by_ids( $action_ids );

return array_filter( $actions, fn( ActionScheduler_Action $action ) => ! $action instanceof ActionScheduler_FinishedAction );
return array_filter( $actions, static fn( ActionScheduler_Action $action ) => ! $action instanceof ActionScheduler_FinishedAction && ! $action instanceof ActionScheduler_NullAction );
}
}
24 changes: 22 additions & 2 deletions src/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Provider extends Provider_Abstract {
* Registers Shepherd's specific providers and starts core functionality
*
* @since 0.0.1
* @since 0.0.7 Updated to register the regulator after the tables are registered successfully.
*
* @return void The method does not return any value.
*/
Expand All @@ -78,19 +79,38 @@ public function register(): void {
$this->container->singleton( Logger::class, Config::get_logger() );
$this->container->singleton( Tables_Provider::class );
$this->container->singleton( Regulator::class );

$prefix = Config::get_hook_prefix();

add_action( "shepherd_{$prefix}_tables_registered", [ $this, 'register_regulator' ] );

if ( ! has_action( "shepherd_{$prefix}_tables_error" ) ) {
_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' );
}

$this->container->get( Tables_Provider::class )->register();
$this->container->get( Regulator::class )->register();

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

self::$has_registered = true;
}

/**
* Registers the regulator.
*
* @since 0.0.7
*
* @return void
*/
public function register_regulator(): void {
$this->container->get( Regulator::class )->register();
}

/**
* Requires Action Scheduler.
*
* @since 0.0.1
* @since 0.0.2
* @since 0.0.2 Look into multiple places for the action scheduler main file.
*
* @return void
*
Expand Down
Loading
Loading