承接 shewa/wp-job-queue 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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
  1. DispatchJobManager::dispatch() validates the job class, checks that the type is registered, ensures no duplicate active job exists for the same type/user, inserts a pending row, and schedules WP-Cron.
  2. ScheduleCronScheduler::schedule() registers a recurring event on the wp_job_queue_worker hook (every minute). spawn_cron() is also called to nudge WP-Cron immediately.
  3. Worker tick — On each cron run (and optionally during admin requests), JobManager::processNext() loads the oldest pending or running job and executes one step.
  4. Step execution — The registered job class is instantiated and handle( JobRecord $job ) is called. It returns a JobResult telling the manager what to do next.
  5. State update — Based on the result:
    • JobResult::continue() — update payload/progress, keep status running, increment current_step
    • JobResult::complete() — set status completed, store final result JSON, set progress to 100
    • JobResult::failed() — set status failed, store the error message
  6. Idle cleanup — When no pending or running jobs 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 as failed.
  • Missing job handlers mark the job as failed with 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

  1. Create a class extending AbstractJob in your plugin (e.g. inc/Jobs/ScrapeEmailsJob.php).
  2. Implement getType(), optionally override getLabel(), and implement handle().
  3. Register the class in your bootstrap config passed to Bootstrap::init().
  4. Dispatch from your feature's AJAX handler or admin action.
  5. 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_CONTINUE instead of CONTINUE).
  • 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
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-12

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固