Skip to content

Conversation

@galatanovidiu
Copy link
Contributor

@galatanovidiu galatanovidiu commented Oct 6, 2025

Summary

Implements a comprehensive category system for organizing abilities. Each ability must now belong to exactly one category, improving discoverability and enabling filtering of abilities by their purpose.


⚠️ Breaking Changes

Required category Parameter

All abilities must now specify a category when registering.

Before:

wp_register_ability( 'my-plugin/get-data', array(
    'label' => 'Get Data',
    'description' => 'Retrieves data',
    // ... other args
));

After:

// First, register a category
add_action( 'abilities_api_category_registry_init', 'my_plugin_register_categories' );
function my_plugin_register_categories() {
    wp_register_ability_category( 'data-retrieval', array(
        'label' => 'Data Retrieval',
        'description' => 'Abilities that retrieve data',
    ));
}

// Then register ability with category
add_action( 'abilities_api_init', 'my_plugin_register_ability' );
function my_plugin_register_ability() {
    wp_register_ability( 'my-plugin/get-data', array(
        'label' => 'Get Data',
        'description' => 'Retrieves data',
        'category' => 'data-retrieval', // REQUIRED
        // ... other args
    ));
}

What's Changed

New Classes

WP_Ability_Category

Encapsulates category properties (slug, label, description).

Location: includes/abilities-api/class-wp-ability-category.php

WP_Abilities_Category_Registry

Singleton registry managing all registered categories.

Location: includes/abilities-api/class-wp-abilities-category-registry.php

Features:

  • Validates category slugs (lowercase alphanumeric + dashes only)
  • Requires label and description for all categories
  • Prevents duplicate category registration
  • Fires abilities_api_category_registry_init hook on initialization
  • Applies register_ability_category_args filter before registration

Core Changes to WP_Ability

File: includes/abilities-api/class-wp-ability.php

  1. Added $category property (required string)
  2. Added get_category() method
  3. Category validation in constructor:
    • Must be non-empty string
    • Must match slug format: ^[a-z0-9]+(-[a-z0-9]+)*$
  4. Category is now part of ability's required properties

Changes to WP_Abilities_Registry

File: includes/abilities-api/class-wp-abilities-registry.php

  1. Added get_abilities_by_category( string $category ) method
  2. Category validation during ability registration:
    • Checks if category exists before registering ability
    • Returns null and triggers _doing_it_wrong() if category not found
  3. Ensures category registry initializes before ability registry

New API Functions

File: includes/abilities-api.php

Category Management

// Register a category
wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category

// Unregister a category
wp_unregister_ability_category( string $slug ): ?WP_Ability_Category

// Get a specific category
wp_get_ability_category( string $slug ): ?WP_Ability_Category

// Get all categories
wp_get_ability_categories(): array

Ability Filtering

// Get abilities by category
wp_get_abilities_by_category( string $category ): WP_Ability[]

New Hooks

Action: abilities_api_category_registry_init

Fires when the category registry is initialized. This is the required hook for registering categories.

add_action( 'abilities_api_category_registry_init', 'my_plugin_register_categories' );
function my_plugin_register_categories( $registry ) {
    wp_register_ability_category( 'my-category', array(
        'label' => 'My Category',
        'description' => 'Description of my category',
    ));
}

Parameters:

  • $registry (WP_Abilities_Category_Registry) - The category registry instance

Filter: register_ability_category_args

Allows modification of category arguments before validation.

add_filter( 'register_ability_category_args', 'my_modify_category_args', 10, 2 );
function my_modify_category_args( array $args, string $slug ): array {
    if ( 'my-category' === $slug ) {
        $args['label'] = 'Modified Label';
    }
    return $args;
}

Parameters:

  • $args (array) - Category arguments (label, description)
  • $slug (string) - Category slug being registered

REST API Updates

File: includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php

New Features

  1. Category field in responses:

    {
      "name": "my-plugin/get-data",
      "label": "Get Data",
      "description": "Retrieves data",
      "category": "data-retrieval",
      "input_schema": {},
      "output_schema": {},
      "meta": {}
    }
  2. Category filtering parameter:

    GET /wp/v2/abilities?category=data-retrieval
  3. Schema updates:

    • category added to ability schema as required field
    • category marked as readonly
    • Available in all contexts (view, edit, embed)

Test Coverage

New Test File: tests/unit/abilities-api/wpAbilityCategory.php (550 lines)

Updated Test Files:

  • wpAbilitiesRegistry.php - Added category setup/teardown
  • wpAbility.php - Added category property to tests
  • wpRegisterAbility.php - Added category validation tests
  • wpRestAbilitiesListController.php - Added category filtering tests (125+ lines)
  • wpRestAbilitiesRunController.php - Updated for category support (56+ lines)

Documentation Updates

1. docs/1.intro.md

  • Added Category to Core Concepts
  • Updated Registry definition to include category registry
  • Updated example to show category registration

2. docs/3.registering-abilities.md (59 new lines)

  • Added category as Required parameter
  • Added "Registering Categories" section with:
    • wp_register_ability_category() function signature
    • Category slug conventions
    • Example category registration code
    • Other category functions documentation
  • Updated all 4 code examples to include category field

3. docs/5.rest-api.md

  • Added category field to Ability Object schema
  • Added category filter parameter to List Abilities endpoint
  • Updated all JSON examples to include category field

4. docs/6.hooks.md (70 new lines)

  • Added abilities_api_category_registry_init action documentation
  • Added register_ability_category_args filter documentation
  • Updated Quick Links navigation

Migration Guide

For Plugin Developers

Step 1: Register Categories

Create categories before registering abilities:

add_action( 'abilities_api_category_registry_init', 'my_plugin_register_categories' );
function my_plugin_register_categories() {
    // Group related abilities into logical categories
    wp_register_ability_category( 'data-retrieval', array(
        'label' => __( 'Data Retrieval', 'my-plugin' ),
        'description' => __( 'Abilities that fetch and return data', 'my-plugin' ),
    ));

    wp_register_ability_category( 'data-modification', array(
        'label' => __( 'Data Modification', 'my-plugin' ),
        'description' => __( 'Abilities that modify or update data', 'my-plugin' ),
    ));
}

Step 2: Update Ability Registrations

Add the category parameter to all wp_register_ability() calls:

add_action( 'abilities_api_init', 'my_plugin_register_abilities' );
function my_plugin_register_abilities() {
    wp_register_ability( 'my-plugin/get-posts', array(
        'label' => __( 'Get Posts', 'my-plugin' ),
        'description' => __( 'Retrieves WordPress posts', 'my-plugin' ),
        'category' => 'data-retrieval', // ADD THIS
        // ... rest of args
    ));
}

Category Slug Naming Conventions

Valid Slugs:

  • data-retrieval
  • user-management
  • ecommerce
  • analytics-123

Invalid Slugs:

  • Data-Retrieval (uppercase)
  • data_retrieval (underscores)
  • data.retrieval (dots)
  • data/retrieval (slashes)
  • -data-retrieval (leading dash)
  • data-retrieval- (trailing dash)

Related Issues

This PR implements the category system from issue #101.

* Add functions to register, unregister, and retrieve ability categories.
* Introduce WP_Ability_Category and WP_Abilities_Category_Registry classes for managing categories.
* Update WP_Ability class to support categories and modify the abilities retrieval process to filter by category.
* Enhance REST API to allow filtering abilities by category and include category information in responses.
* Bump version to 0.3.0 to reflect new features.
* Update the `register` method in `WP_Abilities_Category_Registry` to check for existing slugs before validating format.
* Modify the `WP_Abilities_Registry` class to return abilities as an associative array keyed by ability name.
* Enhance the `WP_Ability_Category` constructor to throw an exception for empty slugs and streamline property assignment.
@galatanovidiu galatanovidiu changed the title Feature/add categories system Feature: add categories system Oct 6, 2025
@codecov
Copy link

codecov bot commented Oct 6, 2025

Codecov Report

❌ Patch coverage is 88.09524% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.48%. Comparing base (4dc57b3) to head (e69d69b).
⚠️ Report is 1 commits behind head on trunk.

Files with missing lines Patch % Lines
...ities-api/class-wp-abilities-category-registry.php 91.42% 6 Missing ⚠️
...cludes/abilities-api/class-wp-ability-category.php 89.36% 5 Missing ⚠️
includes/abilities-api/class-wp-ability.php 16.66% 5 Missing ⚠️
includes/bootstrap.php 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk     #102      +/-   ##
============================================
+ Coverage     86.26%   86.48%   +0.21%     
- Complexity      110      148      +38     
============================================
  Files            16       18       +2     
  Lines           808      969     +161     
  Branches         86       85       -1     
============================================
+ Hits            697      838     +141     
- Misses          111      131      +20     
Flag Coverage Δ
javascript 92.62% <ø> (ø)
unit 84.70% <88.09%> (+0.78%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Move the action hook validation for ability category registration from the public API function into the registry class itself. This change centralizes the validation logic, ensuring it's consistently applied.

The validation is also made more specific, now only permitting registration during the `abilities_api_category_registry_init` action to enforce a stricter and more predictable initialization order.
Update unit tests to register ability categories using the `abilities_api_category_registry_init` action hook.

Previously, tests registered categories after this hook had already fired, which does not reflect the intended API usage. This change ensures that the test setup accurately simulates how categories should be registered, making the test suite more robust and reliable.

A helper method has also been introduced in the `wpAbilityCategory` test class to streamline this process and reduce code duplication.
The `abilities_api_category_registry_init` action hook is renamed to the more concise and intuitive `abilities_api_categories_init`.

This change improves developer experience by making the hook's purpose clearer and aligning it more closely with standard WordPress naming conventions. All related code, documentation, and tests have been updated to use the new hook name.
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few minor notes for the production logic. This looks solid and I'm planning to approve the PR as soon as I review unit tests. Some of my feedback is perfectly suited as follow-up work as it focuses on code quality which might be even easier to review and discuss seperately. @galatanovidiu, can you collect the discussed code quality improvements so we have a good overview of what's left when making the final call? Again, I didn't disovered any blockers so far.

Comment on lines 100 to 117
// Validate category exists if provided (will be validated as required in WP_Ability).
if ( isset( $args['category'] ) ) {
$category_registry = WP_Abilities_Category_Registry::get_instance();
if ( ! $category_registry->is_registered( $args['category'] ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %1$s: category slug, %2$s: ability name */
esc_html__( 'Category "%1$s" is not registered. Please register the category before assigning it to ability "%2$s".' ),
esc_attr( $args['category'] ),
esc_attr( $name )
),
'n.e.x.t'
);
return null;
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it moves most of the validation to the category registry, as the existence of the category name there is the strongest indicator that it has already been validated. No need for additional checks here. There is strong coupling with the category registry, which is fine to have at both levels. @galatanovidiu explained that the basic check for existence still happens inside prepare_properties(), so all checks necessary are covered. I'm fine keeping it here.

@@ -0,0 +1,80 @@
# 7. Registering Categories
Copy link
Member

@gziolo gziolo Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonathanbossenger, we really need to remove these numbers, as I would put this one next to registering categories as it fits better there. It's another instance where this ordering causes trouble 😅

Let's also make sure to list this new document in README somwhere next to:

- [Registering Abilities](docs/3.registering-abilities.md)

Copy link
Member

@gziolo gziolo Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the ordering aspect to a new issue here.

galatanovidiu and others added 5 commits October 13, 2025 13:37
…r clearer context during translation.

Co-authored-by: Greg Ziółkowski <[email protected]>
…r clearer context during translation.

Co-authored-by: Greg Ziółkowski <[email protected]>
…r clearer context during translation.

Co-authored-by: Greg Ziółkowski <[email protected]>
This parts is coverded  in Hooks documentation
Corrects the PHPStan type annotations for wp_register_ability_category()
and WP_Abilities_Category_Registry::register() to accurately reflect the
actual implementation:

- Mark `label` and `description` as required fields (removed optional `?`)
- Add `meta` as an optional property (array<string,mixed>)
- Update docblock to mention `meta` parameter

The label and description fields are validated as required in the
WP_Ability_Category::prepare_properties() method, while meta is
truly optional. The annotations now match the runtime behavior.
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple of additional nitpicks regarding the implementation of tests to consider before landing this PR.

Overall, this is looking excellent functionality-wise from my perspective. Let's make sure that other folks who left feedback are happy with the current shape and plan for merging. I would like to start the process of syncing the abilities registry and REST API layer to WordPress core, and this is the last missing piece of the puzzle I expected 🎉

Comment on lines +451 to +455
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertSame( 'test-math', $result->get_category() );

// Cleanup.
wp_unregister_ability( 'test/calculator' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, I would do the cleanup before assertions to ensure it always happens in case assertions fail for some reason during development.

Suggested change
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertSame( 'test-math', $result->get_category() );
// Cleanup.
wp_unregister_ability( 'test/calculator' );
// Cleanup.
wp_unregister_ability( 'test/calculator' );
$this->assertInstanceOf( WP_Ability::class, $result );
$this->assertSame( 'test-math', $result->get_category() );

galatanovidiu and others added 3 commits October 13, 2025 15:14
Replace loop-based slug validation tests with PHPUnit data providers
for better test isolation and clearer failure reporting.

- Add valid_slug_provider() for valid slug format tests
- Add invalid_slug_provider() for invalid slug format tests
Simplify the test by replacing the callback function with direct calls to register_category_during_hook
@gziolo gziolo mentioned this pull request Oct 13, 2025
- Assert the count of properties in the schema to ensure it matches expected values.
- Verify the existence and details of the 'category' property, including its type and readonly status.
- Confirm that 'category' is included in the required fields of the schema.
- Remove redundant test method for category schema validation.
Copy link
Member

@JasonTheAdams JasonTheAdams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @galatanovidiu! And great discussions, everyone! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants