feat: add AbstractAbility and AbstractAbilityRegistrar - #76
Conversation
There was a problem hiding this comment.
Pull request overview
Adds WordPress Abilities API abstractions, registration support, tests, and consumer guidance.
Changes:
- Introduces
AbstractAbilityandAbstractAbilityRegistrar. - Adds unit and integration coverage for mapping, permissions, registration, and execution.
- Updates framework documentation and AI instructions.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
inc/Contracts/Abstracts/AbstractAbility.php |
Defines the ability contract and registration arguments. |
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php |
Registers categories and abilities on API hooks. |
tests/Contracts/Abstracts/AbstractAbilityTest.php |
Tests argument mapping and permissions. |
tests/Contracts/Abstracts/AbstractAbilityRegistrarTest.php |
Tests registration and execution round-trips. |
README.md |
Lists the new abstracts. |
docs/index.md |
Updates abstract counts and navigation. |
docs/abstracts.md |
Adds the Abilities API cookbook. |
ai/framework-php.instructions.md |
Adds consumer review guidance. |
.github/instructions/php.instructions.md |
Updates the framework abstract inventory. |
AGENTS.md |
Documents WordPress-version compatibility. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (3)
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:54
- The docblock says the function_exists guards are for partial backports, but
register_category()only guardswp_register_ability_category()and then unconditionally callswp_has_ability_category(). On a partial backport wherewp_register_ability_category()exists butwp_has_ability_category()does not, this will fatal. Consider guardingwp_has_ability_category()as well (or using a more defensive check that doesn’t require it).
public function register_category(): void {
if ( ! function_exists( 'wp_register_ability_category' ) ) {
return;
}
if ( wp_has_ability_category( $this->category_slug() ) ) {
return;
}
inc/Contracts/Abstracts/AbstractAbility.php:7
@since 1.0.0appears inconsistent with the rest of this PR (tests are annotated@since 0.0.1, and the repo/package versioning in the PR metadata suggests this is part of the current pre-1.0 series). If 1.0.0 is not the intended release version for this addition, align@sincetags to the correct package version to avoid misleading generated docs/changelogs.
/**
* Abstract Ability.
*
* @package rtCamp\WPFramework\Contracts\Abstracts
* @since 1.0.0
*/
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:77
- Because
abilities()is typed asarrayat the signature level, an accidental non-AbstractAbilityvalue will cause a hard fatal when callingname()/args(). Consider adding a lightweight runtime validation (e.g., skip/throw with a clear message) to make contract violations easier to diagnose in consumer code.
foreach ( $this->abilities() as $ability ) {
wp_register_ability( $ability->name(), $ability->args() );
}
Telemetry-agnostic base classes for the WordPress Abilities API (6.9+), lifted from the shape proven in rtcamp/wp-dev-tools (rtCamp/wp-devtools#12, item 1). AbstractAbility declares the pieces of an ability and maps them to wp_register_ability() args; AbstractAbilityRegistrar (a Registrable) hooks wp_abilities_api_categories_init / wp_abilities_api_init, registers the shared category idempotently, and loops its abilities. Both callbacks are wrapped in defaulted closures because core invokes them with zero arguments when no input schema is declared. category_description() is abstract: core rejects categories without a non-empty description. On cores older than 6.9 the hooks never fire, so the registrar is inert and the package's WordPress floor is unchanged.
Abstracts cookbook gains an Abilities section (must-implement list, override seams, shared-category consumer example, the WP <6.9 inertness note); README and docs index updated. The AI-instruction ability lists now name the ability abstracts and wp_register_ability, and pick up AbstractFeature, which was missing from both since it landed.
90bfbd3 to
55ade71
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:54
- This method guards
wp_register_ability_category()but still callswp_has_ability_category()unconditionally. If the Abilities API is partially available (which the docblock explicitly mentions), this can fatal whenwp_has_ability_category()is missing. Suggest guarding the existence of all Abilities API functions used here (or using a single capability check that implies all required functions are present) before calling them.
public function register_category(): void {
if ( ! function_exists( 'wp_register_ability_category' ) ) {
return;
}
if ( wp_has_ability_category( $this->category_slug() ) ) {
return;
}
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:42
- Both callbacks (
register_category()/register_abilities()) declare zero parameters, butadd_action()defaults$accepted_argsto 1. Setting$accepted_argsto 0 here avoids accidentalArgumentCountErrorif the core hooks ever pass an argument (and documents the intended signature).
public function register_hooks(): void {
add_action( 'wp_abilities_api_categories_init', [ $this, 'register_category' ] );
add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] );
}
inc/Contracts/Abstracts/AbstractAbility.php:6
- The
@sincetag is set to1.0.0, but the PR/testing context indicates the package version is still0.0.1. Please align@sinceto the actual version this abstraction is being introduced in (and keep it consistent with the surrounding codebase/doc conventions).
* @package rtCamp\WPFramework\Contracts\Abstracts
* @since 1.0.0
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:6
- Same as
AbstractAbility.php: the@sincetag is1.0.0but appears inconsistent with the current package version in this repo/tests. Please update@sinceto the actual introduction version and keep the two new abstractions consistent.
* @package rtCamp\WPFramework\Contracts\Abstracts
* @since 1.0.0
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (3)
inc/Contracts/Abstracts/AbstractAbilityRegistrar.php:54
register_category()guardswp_register_ability_category()but callswp_has_ability_category()unguarded, which can fatally error in “partial backport” scenarios (the docblock explicitly mentions these). Consider guardingwp_has_ability_category()withfunction_exists()and falling back to unconditional registration when it’s missing (or skipping the idempotency check in that case).
public function register_category(): void {
if ( ! function_exists( 'wp_register_ability_category' ) ) {
return;
}
if ( wp_has_ability_category( $this->category_slug() ) ) {
return;
}
inc/Contracts/Abstracts/AbstractAbility.php:6
- The new classes are tagged
@since 1.0.0, but the new test classes are tagged@since 0.0.1and the PR context indicates the package is currently0.0.1. Please align the@sincetags to the actual first release version that will ship these classes (or update the rest of the repo docs/tests if1.0.0is correct).
* @package rtCamp\WPFramework\Contracts\Abstracts
* @since 1.0.0
tests/Contracts/Abstracts/AbstractAbilityTest.php:116
- This test asserts the default permission callback returns
falsebut doesn’t set the current user. If the test suite (orTestCase) doesn’t reliably reset the current user between tests, this can become order-dependent/flaky. Consider explicitly callingwp_set_current_user( 0 )(or otherwise setting a known user) within this test before assertingfalse.
public function test_callbacks_are_safe_to_invoke_without_arguments(): void {
// Core invokes both callbacks with zero arguments when the ability
// declares no input schema; the closure wrappers absorb that.
$args = $this->make_ability()->args();
$this->assertSame( [ 'received' => null ], $args['execute_callback']() );
$this->assertFalse( $args['permission_callback']() );
}
What this PR does
Add AbstractAbility and AbstractAbilityRegistrar — telemetry-agnostic base classes for the WordPress 6.9+ Abilities API. The telemetry-shaped AbstractAbility from wp-dev-tools is decomposed: registration timing and category ownership live on the registrar, the ability stays a plain describable object. Both are hook-guarded, so the package's 6.5 floor is unchanged.
Closes
Part of rtcamp/wp-devtools#12
Changes
How I verified
Acceptance criteria
Runtime behavior
AbstractAbilityandAbstractAbilityRegistrarexist inwp-frameworkwith unit tests; a trivial ability can be defined and registers onwp_abilities_api_init.Code quality
Reviewer notes