feat(contracts): add AbstractJob and the platform-extensibility abstracts - #85
feat(contracts): add AbstractJob and the platform-extensibility abstracts#85Adi-ty wants to merge 5 commits into
Conversation
0a78af5 to
ca4bb81
Compare
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds new shared, dependency-free framework contracts for background jobs and platform/extensibility seams, along with tests, PHPStan stubs, and documentation updates.
Changes:
- Introduces
AbstractJob(Action Scheduler–guarded scheduling with synchronous WP-hook fallback) - Adds four platform/extensibility abstract contracts (local env, platform logs, platform profiles, vulnerability providers)
- Adds PHPUnit tests + Action Scheduler fakes + PHPStan stub wiring; updates docs and AI instruction references
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
inc/Contracts/Abstracts/AbstractJob.php |
New background-job base class that schedules via Action Scheduler when available. |
inc/Contracts/Abstracts/AbstractLocalEnvironment.php |
New contract for local dev-environment detection and capabilities. |
inc/Contracts/Abstracts/AbstractPlatformLog.php |
New contract for reading/parsing host log entries with a shared parser helper. |
inc/Contracts/Abstracts/AbstractPlatformProfile.php |
New contract for platform constraint “buckets” with permissive defaults. |
inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php |
New contract for WP ecosystem vulnerability lookup + shared affects_version(). |
tests/bootstrap.php |
Loads Action Scheduler fakes for tests. |
tests/Fixtures/ActionSchedulerFakes.php |
Provides global Action Scheduler API fakes used by AbstractJob tests. |
tests/Contracts/Abstracts/AbstractJobTest.php |
Covers AbstractJob scheduling + hook registration behavior against fakes. |
tests/Contracts/Abstracts/AbstractLocalEnvironmentTest.php |
Covers default behavior + overrides for local environment contract. |
tests/Contracts/Abstracts/AbstractPlatformLogTest.php |
Covers parsing and availability behavior for platform log contract. |
tests/Contracts/Abstracts/AbstractPlatformProfileTest.php |
Verifies permissive defaults and override propagation for profiles. |
tests/Contracts/Abstracts/AbstractVulnerabilityProviderTest.php |
Tests affects_version() logic and default availability behavior. |
phpstan.neon.dist |
Adds scanFiles for Action Scheduler analysis stub. |
.stubs/action-scheduler.stub |
Declares Action Scheduler symbols for PHPStan without adding a dependency. |
docs/index.md |
Updates abstract count and clarifies queried-on-demand abstracts. |
docs/abstracts.md |
Documents AbstractJob and platform-extensibility abstracts. |
README.md |
Updates abstract list/count to include new contracts. |
AGENTS.md |
Documents optional-dependency seam approach for Action Scheduler. |
ai/framework-php.instructions.md |
Updates agent guidance to prefer new abstracts/contracts. |
.github/instructions/php.instructions.md |
Updates framework rules to include the new abstracts and their shape. |
Suppressed comments (1)
docs/abstracts.md:1
- Subject/verb agreement: the parenthetical refers to multiple abstracts, so it should read “follow the same shape” rather than “follows the same shape”.
# Abstracts — the base-class cookbook
…nd jobs Lifts a background-job contract into wp-framework per rtCamp/wp-devtools#12. Action Scheduler is never a dependency of this package — every scheduling method guards on is_available() and no-ops when the library isn't loaded. register_hooks() always attaches a plain WordPress action, so handle() runs synchronously via do_action() even without Action Scheduler installed. PHPStan resolves the as_*() functions via a hand-written analysis-only stub (.stubs/action-scheduler.stub); tests exercise both branches deterministically against fakes in tests/Fixtures/ActionSchedulerFakes.php rather than the real library, since the real dependency belongs on the consuming plugin, not here.
Four vendor-neutral seams that later packages fill in with concrete implementations (rtCamp/wp-devtools#12, item 3): AbstractPlatformLog reads recent host log entries and normalizes them to level/message/file:line, AbstractPlatformProfile carries the ruleset a readiness lens applies, AbstractLocalEnvironment sits behind wp-env and other local stacks, and AbstractVulnerabilityProvider fronts a CVE database. None implement Registrable: they register no WordPress hook and are queried on demand, so the Loader has nothing to drive. Every rule bucket and seam defaults to empty or permissive, which keeps a bare subclass useful and lets new buckets land later without breaking existing ones. The base classes own the two pieces of shared logic worth centralising: parse_error_log_line() handles both PHP error-log formats (FILE:LINE and "on line LINE") and returns null for stack frames, and affects_version() resolves a vulnerability's fixed_in against an installed version.
Abstracts cookbook gains a Background jobs section (Action Scheduler as an optional seam, the single-array args contract, the schedule_*() surface) and a Platform extensibility section covering the four queried-on-demand contracts, each with a consumer example. Hook table, README, and docs index updated; the count is now seventeen. The AI-instruction class lists pick up all five, rule 5 gains the as_*() scheduling functions, and AGENTS.md records that an optional third-party library is a guarded seam rather than a dependency.
ca4bb81 to
198ed44
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
tests/Fixtures/ActionSchedulerFakes.php:22
- These global-namespace classes trigger Composer PSR-4 warnings during
composer install(as shown in the PR's verification output). To avoid noisy installs (and potential CI strictness), consider either (1) moving the implementation underrtCamp\\WPFramework\\Tests\\Fixtures\\...andclass_alias()-ing into the global names when bootstrapped, or (2) excluding this file from autoload-dev classmap generation (e.g.,exclude-from-classmap) since it's manuallyrequire_once'd anyway.
if ( ! class_exists( 'ActionScheduler', false ) ) {
final class ActionScheduler {
/**
* Toggle to simulate the library being unavailable; reset via as_fakes_reset().
*/
public static bool $initialized = true;
inc/Contracts/Abstracts/AbstractJob.php:52
- The hook callback parameter is strictly typed as
array, which will throw aTypeErrorif the action is fired with a non-array argument (easy to do accidentally because this is a plain WP action). If you want the job hook to be robust, acceptmixed $args = []here and normalize to an array before callinghandle()(while still keepinghandle(array $args)as the contract).
add_action(
static::get_hook(),
function ( array $args = [] ): void {
$this->handle( $args );
},
$this->get_priority(),
1
);
inc/Contracts/Abstracts/AbstractJob.php:192
- The uniqueness behavior description is inaccurate/incomplete: Action Scheduler uniqueness is based on hook + args + group (not just hook + group). Since this class intentionally wraps args as
[ $args ], it would be clearer to document uniqueness in terms of the exact hook/group/wrapped-args tuple used by this abstraction.
* Whether a scheduled action should be unique.
*
* When true, Action Scheduler skips scheduling if a pending or running
* action already exists with the same hook and group.
*
* @return bool
*/
tests/Fixtures/ActionSchedulerFakes.php:99
- The recurring-action fake ignores
$interval_in_secondsentirely, which can make future tests misleading (e.g., any logic that inspects the schedule cadence can't be exercised). Consider storinginterval_in_secondsinActionSchedulerFakeStore::$actionsfor recurring actions (even if nothing consumes it today) to keep the fake closer to the real API.
if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
function as_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
return ActionSchedulerFakeStore::add( $hook, $args, $group, $unique, $timestamp );
}
}
…rd queue priority to as_* calls
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
inc/Contracts/Abstracts/AbstractJob.php:49
- The hook callback hard-types the first parameter as
array. In WordPress,do_action()callers can pass any scalar/object; if a consumer accidentally calls the hook with a non-array first argument, this will throw aTypeErrorat runtime. To keep the contract (single associative array) without hard-failing, consider accepting an untyped$argsand normalizing (e.g., treat non-arrays as[]) before passing intohandle().
add_action(
static::get_hook(),
function ( array $args = [] ): void {
$this->handle( $args );
},
composer.json:38
- Composer's PSR-4 compliance warnings in the PR output suggest these global-namespace fixture classes are still being scanned.
exclude-from-classmapis not consistently honored underautoload-devacross Composer versions/configs; the more reliable fix is to ensure these fixtures are not under a PSR-4 scanned path (e.g., move them into a dedicated non-PSR-4 directory loaded viarequire_once), or place the exclude where Composer will actually apply it for the generated optimized autoloader in this repo’s setup.
"autoload-dev": {
"psr-4": {
"rtCamp\\WPFramework\\Tests\\": "tests/"
},
"exclude-from-classmap": [
"tests/Fixtures/ActionSchedulerFakes.php",
"tests/Fixtures/LoaderFixtures.php"
]
…ad after WP bootstrap
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (4)
tests/Fixtures/ActionSchedulerFakes.php:45
- These global-namespace classes live under the
tests/PSR-4 autoload-dev tree, which is what triggers the Composer PSR-4 compliance warnings shown in the PR’s verification output. To eliminate the warnings (and keep the fakes), consider moving the classes into thertCamp\\WPFramework\\Tests\\Fixturesnamespace and thenclass_alias()-ing them into the global names (ActionScheduler,ActionSchedulerFakeStore) when fakes are active, or alternatively relocating this file outside the PSR-4-mappedtests/directory and continuing torequire_onceit fromtests/bootstrap.php.
if ( ! class_exists( 'ActionScheduler', false ) ) {
final class ActionScheduler {
/**
* Toggle to simulate the library being unavailable; reset via as_fakes_reset().
*/
public static bool $initialized = true;
public static function is_initialized( ?string $function_name = null ): bool {
return self::$initialized;
}
}
}
if ( ! class_exists( 'ActionSchedulerFakeStore', false ) ) {
/**
* In-memory scheduled-action store backing the as_*() fakes below.
*/
final class ActionSchedulerFakeStore {
/** @var array<int, array{hook: string, args: array<mixed>, group: string, timestamp: ?int, priority: int}> */
public static array $actions = [];
inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php:58
- The lookup return-shape docblocks require a
fixed_inkey (fixed_in: ?string), butaffects_version()explicitly supports it being omitted (fixed_in?: ?string) and the tests cover the missing-key case. To keep the contract consistent for implementers, either update the return-shape annotations to makefixed_inoptional (fixed_in?: ?string) or tightenaffects_version()/tests to require the key.
/**
* Return every known vulnerability for a plugin.
*
* @param string $slug Plugin slug, e.g. "akismet".
*
* @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
*/
abstract public function get_plugin_vulnerabilities( string $slug ): array;
/**
* Return every known vulnerability for a theme.
*
* @param string $slug Theme slug, e.g. "twentytwentyfour".
*
* @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
*/
abstract public function get_theme_vulnerabilities( string $slug ): array;
/**
* Return every known vulnerability for a WordPress core version.
*
* @param string $version Core version, e.g. "6.5.2".
*
* @return array<int, array{title: string, type: string, fixed_in: ?string, references: array{url: string[], cve: string[]}, cvss: ?array{score: float, vector: string}}> Vulnerabilities.
*/
abstract public function get_core_vulnerabilities( string $version ): array;
tests/Fixtures/ActionSchedulerFakes.php:47
- The recurring-action fake accepts
$interval_in_secondsbut drops it on the floor (it’s neither stored nor used), which makes the fake diverge from the real API and can hide bugs in code that depends on interval behavior. Consider extendingActionSchedulerFakeStore::$actionsto include aninterval_in_seconds(or similar) field and passing it through fromas_schedule_recurring_action()so future tests can assert on it accurately.
/** @var array<int, array{hook: string, args: array<mixed>, group: string, timestamp: ?int, priority: int}> */
public static array $actions = [];
private static int $next_id = 1;
tests/Fixtures/ActionSchedulerFakes.php:109
- The recurring-action fake accepts
$interval_in_secondsbut drops it on the floor (it’s neither stored nor used), which makes the fake diverge from the real API and can hide bugs in code that depends on interval behavior. Consider extendingActionSchedulerFakeStore::$actionsto include aninterval_in_seconds(or similar) field and passing it through fromas_schedule_recurring_action()so future tests can assert on it accurately.
if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
function as_schedule_recurring_action( int $timestamp, int $interval_in_seconds, string $hook, array $args = [], string $group = '', bool $unique = false, int $priority = 10 ): int {
return ActionSchedulerFakeStore::add( $hook, $args, $group, $unique, $timestamp, $priority );
}
}
What this PR does
Adds five shared framework contracts for background jobs and platform extensibility — each a pure seam with zero platform/vendor-specific code and zero runtime dependencies.
The four platform abstracts are deliberately not Registrable — they register no WordPress hook and are constructed/queried on demand.
Closes
Closes rtcamp/wp-devtools#12
Changes
inc/Contracts/Abstracts/AbstractJob.php— new; schedule_async(), schedule_at(), schedule_recurring(), handle(), register_hooks() sync fallback, single-array args contract, Action Scheduler guarded behind is_available().inc/Contracts/Abstracts/AbstractLocalEnvironment.php— new; get_name(), is_active(), get_debug_log_path(), get_available_services(), is_filesystem_writable().inc/Contracts/Abstracts/AbstractPlatformLog.php— new; get_recent_entries(), is_available(), parse_error_log_line() → host file:line.inc/Contracts/Abstracts/AbstractPlatformProfile.php— new; permissive default rule buckets, abstract get_slug() / get_name().inc/Contracts/Abstracts/AbstractVulnerabilityProvider.php— new; plugin/theme/core lookup seams, affects_version(), is_available() default true.tests/Contracts/Abstracts/— five new TDD test classes (pure-logic on PHPUnit\Framework\TestCase).tests/Fixtures/ActionSchedulerFakes.php— new; fakes for the guarded seam (global as_*() functions + toggleable ActionScheduler::$initialized)..stubs/action-scheduler.stub— new, wired into phpstan.neon.dist scanFiles so PHPStan resolves the optional library.tests/bootstrap.php— requires the Action Scheduler fakes.docs/abstracts.md(background jobs + platform extensibility), docs/index.md, README hook-table count → seventeen; AGENTS.md and AI instruction files (ai/, .github/instructions/) synced via bin/sync-ai-instructions.js.How I verified
Acceptance criteria
Runtime behavior
Code quality