shewa/wp-job-queue
Composer 安装命令:
composer require shewa/wp-job-queue
包简介
A reusable WordPress background job queue powered by WP-Cron.
README 文档
README
A reusable WordPress background job queue powered by WP-Cron. Jobs are stored in a custom database table, processed one step at a time, report progress to the admin UI, and can be cancelled while running.
Requirements
- PHP 7.4 through 8.5
- WordPress 6.0+
Installation
composer require shewa/wp-job-queue
On plugin activation, install the database table:
use WpJobQueue\Database\Migrator; register_activation_hook( __FILE__, function () { Migrator::install(); } );
Quick start
use WpJobQueue\Bootstrap; use WpJobQueue\JobManager; // Boot on plugins_loaded Bootstrap::init( [ 'jobs' => [ MyCustomJob::class, ], ] ); // Dispatch a job $job_id = JobManager::instance()->dispatch( MyCustomJob::class, [ 'step' => 0 ] ); if ( is_wp_error( $job_id ) ) { // Handle error }
Architecture
wp-job-queue/
├── src/
│ ├── AbstractJob.php # Base class to extend
│ ├── Bootstrap.php # Package bootstrapping
│ ├── JobManager.php # Dispatch, process, cancel
│ ├── JobRegistry.php # Job type → class mapping
│ ├── JobRepository.php # Database access
│ ├── JobRecord.php # Job value object
│ ├── JobResult.php # Step result object
│ ├── JobStatus.php # Status constants
│ ├── Contracts/JobInterface.php
│ ├── Cron/CronScheduler.php # WP-Cron worker
│ ├── Database/Migrator.php # Table installer
│ └── Http/AjaxHandler.php # Generic AJAX endpoints
| Component | Responsibility |
|---|---|
Bootstrap |
Registers jobs, cron hooks, AJAX handlers, and runs DB upgrades |
JobRegistry |
Maps job type strings (e.g. export_websites) to PHP classes |
JobManager |
Public API: dispatch, process, cancel, list, and get jobs |
JobRepository |
All reads/writes to the {prefix}wp_job_queue_jobs table |
CronScheduler |
Schedules and runs the WP-Cron worker every minute |
AjaxHandler |
REST-like AJAX endpoints for polling and cancelling from JavaScript |
AbstractJob |
Base class your job handlers extend |
How it works under the hood
WP Job Queue uses a step-based processing model. Each job is not one long-running task — it is a record in the database that advances one small chunk of work per execution. This fits WordPress well because PHP requests and WP-Cron ticks are short-lived.
Lifecycle
dispatch() → pending → running → (step loop) → completed
↘ failed
↘ cancelled
- Dispatch —
JobManager::dispatch()validates the job class, checks that the type is registered, ensures no duplicate active job exists for the same type/user, inserts apendingrow, and schedules WP-Cron. - Schedule —
CronScheduler::schedule()registers a recurring event on thewp_job_queue_workerhook (every minute).spawn_cron()is also called to nudge WP-Cron immediately. - Worker tick — On each cron run (and optionally during admin requests),
JobManager::processNext()loads the oldestpendingorrunningjob and executes one step. - Step execution — The registered job class is instantiated and
handle( JobRecord $job )is called. It returns aJobResulttelling the manager what to do next. - State update — Based on the result:
JobResult::continue()— update payload/progress, keep statusrunning, incrementcurrent_stepJobResult::complete()— set statuscompleted, store final result JSON, set progress to 100JobResult::failed()— set statusfailed, store the error message
- Idle cleanup — When no
pendingorrunningjobs remain, the cron event is unscheduled to avoid unnecessary overhead.
Processing triggers
Jobs advance through two mechanisms:
| Trigger | When | Purpose |
|---|---|---|
WP-Cron (wp_job_queue_worker) |
Every minute while jobs are active | Primary background worker |
Admin init (maybeProcessOnAdmin) |
Each admin page load by a privileged user | Helps local/dev sites where cron is unreliable |
Concurrency model
- Only one step of one job runs per worker invocation.
- Only one active job per type per user is allowed at dispatch time (prevents duplicate exports, etc.).
- Jobs are processed FIFO by
created_at.
Error handling
- Uncaught exceptions in
handle()are caught and mark the job asfailed. - Missing job handlers mark the job as
failedwith a clear message. - Cancelled jobs are skipped on subsequent steps if the handler checks status early.
WP-Cron note
WordPress cron only fires when someone visits the site. For production, either prompt users to keep the admin page open while jobs run, or configure a real server cron:
*/1 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Database table
Table name: {prefix}wp_job_queue_jobs (override with the wp_job_queue_table_name filter)
| Column | Description |
|---|---|
id |
Auto-increment primary key |
job_type |
Unique job identifier (from getType()) |
status |
pending, running, completed, failed, or cancelled |
payload |
JSON data passed between steps |
progress |
0–100 percentage |
current_step |
Number of completed steps |
total_steps |
Total steps if known (0 otherwise) |
result |
JSON output when completed |
error_message |
Error text when failed |
status_message |
Human-readable progress message |
user_id |
User who dispatched the job |
created_at / updated_at |
Timestamps |
started_at / completed_at |
Set when job starts and reaches a terminal state |
The table is created by Migrator::install() and auto-upgraded on boot via Migrator::maybeUpgrade().
Creating a job
Extend AbstractJob and implement handle(). Each call to handle() should process one step and return a JobResult.
<?php namespace MyPlugin\Jobs; use WpJobQueue\AbstractJob; use WpJobQueue\JobRecord; use WpJobQueue\JobResult; use WpJobQueue\JobStatus; class MyCustomJob extends AbstractJob { public static function getType(): string { return 'my_custom_job'; } public static function getLabel(): string { return 'My Custom Job'; } public function handle( JobRecord $job ): JobResult { if ( JobStatus::CANCELLED === $job->status ) { return JobResult::failed( 'Job was cancelled.' ); } $payload = $job->payload; $step = (int) ( $payload['step'] ?? 0 ); // Do one chunk of work… $step++; $payload['step'] = $step; if ( $step < 10 ) { return JobResult::continue( $payload, $step * 10, sprintf( 'Processing step %d…', $step ) ); } return JobResult::complete( [ 'done' => true ], 'All steps finished.' ); } }
Register the class when booting:
Bootstrap::init( [ 'jobs' => [ MyCustomJob::class, ], ] );
JobResult API
| Method | When to use |
|---|---|
JobResult::continue( $payload, $progress, $message ) |
More steps remain |
JobResult::complete( $result, $message ) |
Job finished successfully |
JobResult::failed( $error ) |
Job failed |
Status constants (for comparisons):
| Constant | Value |
|---|---|
JobResult::STATUS_CONTINUE |
continue |
JobResult::STATUS_COMPLETE |
complete |
JobResult::STATUS_FAILED |
failed |
Dispatching jobs
use WpJobQueue\JobManager; $job_id = JobManager::instance()->dispatch( MyCustomJob::class, [ 'step' => 0 ], get_current_user_id() // optional; defaults to current user ); if ( is_wp_error( $job_id ) ) { // Possible codes: invalid_job, unregistered_job, job_already_running, job_create_failed }
Checking job status
PHP
use WpJobQueue\JobManager; use WpJobQueue\JobStatus; $job = JobManager::instance()->getJob( $job_id ); $active_jobs = JobManager::instance()->listJobs( [ 'job_type' => 'my_custom_job', 'status' => JobStatus::active(), ] );
JobStatus::active() returns [ 'pending', 'running' ].
JobStatus::terminal() returns [ 'completed', 'failed', 'cancelled' ].
AJAX (JavaScript polling)
All endpoints require manage_options and a valid nonce.
| Action | Parameters | Description |
|---|---|---|
wp_job_queue_status |
job_id, wp_job_queue_nonce |
Get a single job |
wp_job_queue_cancel |
job_id, wp_job_queue_nonce |
Cancel a job |
wp_job_queue_list |
job_type, status[], wp_job_queue_nonce |
List jobs |
Nonce action: wp_job_queue
Nonce field: wp_job_queue_nonce
wp_create_nonce( \WpJobQueue\Bootstrap::nonceAction() );
Example JavaScript poll:
const data = new FormData(); data.append( 'action', 'wp_job_queue_status' ); data.append( 'job_id', jobId ); data.append( 'wp_job_queue_nonce', nonce ); fetch( ajaxurl, { method: 'POST', body: data, credentials: 'same-origin' } ) .then( ( response ) => response.json() ) .then( ( response ) => { if ( response.success ) { console.log( response.data.job.progress, response.data.job.status_message ); } } );
Cancelling jobs
$result = JobManager::instance()->cancel( $job_id ); if ( is_wp_error( $result ) ) { // job_not_found, unauthorized, cancel_failed }
Jobs should check for cancellation at the start of handle() if they need to stop mid-run:
if ( JobStatus::CANCELLED === $job->status ) { return JobResult::failed( 'Job was cancelled.' ); }
Example: multi-step API pagination
A common pattern is fetching paginated API results across multiple steps:
public function handle( JobRecord $job ): JobResult { $payload = $job->payload; $page = (int) ( $payload['page'] ?? 1 ); $items = (array) ( $payload['items'] ?? [] ); $response = $this->fetchPage( $page ); if ( is_wp_error( $response ) ) { return JobResult::failed( $response->get_error_message() ); } $items = array_merge( $items, $response['items'] ); $total = (int) $response['total_pages']; $page++; if ( $page <= $total ) { return JobResult::continue( [ 'page' => $page, 'items' => $items ], (int) round( ( $page / $total ) * 100 ), sprintf( 'Fetched page %d of %d…', $page - 1, $total ) ); } return JobResult::complete( [ 'items' => $items, 'count' => count( $items ) ], 'Export complete.' ); }
Adding new jobs
- Create a class extending
AbstractJobin your plugin (e.g.inc/Jobs/ScrapeEmailsJob.php). - Implement
getType(), optionally overridegetLabel(), and implementhandle(). - Register the class in your bootstrap config passed to
Bootstrap::init(). - Dispatch from your feature's AJAX handler or admin action.
- Use the built-in AJAX endpoints to poll progress and cancel from the frontend.
Filters
| Filter | Description |
|---|---|
wp_job_queue_table_name |
Override the database table name |
add_filter( 'wp_job_queue_table_name', function ( $table ) { return $GLOBALS['wpdb']->prefix . 'my_custom_jobs'; } );
PHP compatibility
This package targets PHP 7.4 through 8.5. It uses features available in PHP 7.4 (typed properties, nullable types, void return types) and avoids syntax that would break on older runtimes:
- Reserved keywords are not used as class constant names (
STATUS_CONTINUEinstead ofCONTINUE). - JSON columns are decoded defensively to avoid type warnings on PHP 8+.
- Database row properties use null coalescing to handle partial rows safely.
Repository
https://github.com/shewa12/wp-job-queue
License
MIT
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-12