This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

dev!: handle property registration inside WP_Ability - #54

Merged
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration
Sep 5, 2025
Merged

dev!: handle property registration inside WP_Ability#54
gziolo merged 2 commits into
WordPress:trunkfrom
justlevine:dev/WP_Ability-property-registration

Conversation

@justlevine

@justlevinejustlevine commented Sep 2, 2025

Copy link
Copy Markdown
Contributor

What

Refactors WP_Ability to validate its own properties.

How

WP_Ability::validate_properties() throws an exception, which is caught and translated into a _doing_it_wrong() by WP_Abilities_API::register() which was previously handled registration and validation.

More specific implementation notes are in the diff.

Why

This approach improves separation of concerns and SRP between WP_Ability and WP_Ability_Registry in a way that also simplifies the DX for users needing to extend the WP_Ability class. Instead of needing to shim instantiation to comply or circumvent WP_Ability_Registry, developers can overload WP_Ability::validate_properties() colocated with the other changes that justify extending the class, or even choose to bypass it entirely in their overloaded constructor.

Beyond the Scope

  • Renaming references of ability properties to args now that there's no semantic implication from the distinction.
  • Auditing the validation logic, error messages, or use of _doing_it_wrong() over other flavors of WordPress error handling
  • Backfilling WPUnit tests for previously committed validation logic..

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors the WP_Ability class to handle its own property validation, moving validation logic from WP_Abilities_Registry into the ability class itself. This improves separation of concerns and simplifies the developer experience when extending the WP_Ability class.

  • Moved property validation from WP_Abilities_Registry::register() to WP_Ability::validate_properties()
  • Added exception handling in the registry to catch validation errors and convert them to _doing_it_wrong() calls
  • Added test coverage for direct instantiation of WP_Ability with invalid properties

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

FileDescription
includes/abilities-api/class-wp-ability.phpAdded validate_properties() method and validation call in constructor
includes/abilities-api/class-wp-abilities-registry.phpRemoved validation logic and added try-catch block for ability instantiation
tests/unit/abilities-api/wpAbilitiesRegistry.phpAdded test for exception throwing when WP_Ability is instantiated with invalid properties

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@codecov

codecovBot commented Sep 2, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 51.28205% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (1b75e1d) to head (342e414).
⚠️ Report is 1 commits behind head on trunk.

Files with missing linesPatch %Lines
includes/abilities-api/class-wp-ability.php40.00%18 Missing ⚠️
...udes/abilities-api/class-wp-abilities-registry.php88.88%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## trunk #54 +/- ##
============================================
- Coverage 88.43% 84.61% -3.83% - Complexity 94 96 +2 
============================================
Files 8 8 Lines 519 507 -12 ============================================
- Hits 459 429 -30 - Misses 60 78 +18 
FlagCoverage Δ
unit84.61% <51.28%> (-3.83%)⬇️

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.

* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

A small part of me thinks that we should change all the references of properties to args to bring it inline with other WP core naming, now that we've broken the direct dependency to add a validator. 🤷

I could add it in this PR but i didnt want to obfuscate the discussion on #53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have now ability_class which isn't strictly a property of the object, so I don't mind changing to args at this point. If you want to take care of it, let's put it into a separate PR to keep the current refactor lean.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 to changing this to use $args.

Comment on lines +107 to 109
$this->validate_properties( $properties );

foreach ( $properties as $property_name => $property_value ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

An alternative approach is to replace the validate_*() pattern with the prepare_*(): array|WP_Error() , and then throw in the constructor.

This would improve the DX both mapping and validation in the same step, but I'm not sure how flexible we want things to be at this initial stage vs once we've given the initial API + round of feedback time to percolate, so I went with the approach in the diff.

e.g. (pseudocode)

$properties = $this->prepare_args( $args );
if ( is_wp_error( $properties ) ) {
throw\InvalidArgumentException( $properties->getMessage() );
}
// This is still outside the function so extenders don't need to reimplement it.foreach( $propertiesas$name => $value ) {
...
}

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@gziolo / @felixarntz would love some specific thoughts on this if you have them. The more I think about it the more I feel like protected function prepare_properties( array<string,mixed> ): array<validated-shape>|WP_Error is the better approach 🤔

@gziologzioloSep 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so you prefer to have prepare_properties that returns WP_Error as soon as something goes wrong, and it gets translated to an exception, or the properties returned gets passed to the loop. That sounds good to me, to avoid having a method that only throws an exception when someting goes wrong. In fact, it might be simpler to ever introduce a filter if folks want to change this properties as an alternative to what I proposed here:

The filter used with block types register_block_type_args comes to my find as a good reference:

https://github.com/WordPress/wordpress-develop/blob/9b6c234bc6a78ce4a57929ea5413d0b08a3d706e/src/wp-includes/class-wp-block-type.php#L565-L573

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

My thoughts exactly. After the chat, I'll push a change with that approach.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not strongly opposed, but I also don't see the benefit of using prepare_properties and returning a WP_Error from it if something is wrong. Can you clarify why that's better?

To me it seems unnecessarily complex to rely on a method that can return WP_Error, only to turn that into an InvalidArgumentException, only to catch that and turn it into a _doing_it_wrong().

Why not simply throw the exceptions?

@justlevinejustlevineSep 3, 2025

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Clarifying that my focus is on returning the array of (prepared) args instead of void. 😅

@@felixarntz I agree with you from a code quality POV

From an implementer POV my thought was less "exceptions should be exceptional" and more "let's do it the WP™️ way 💪" :

  • most the other parts of the exposed API return WP_Errors to be handled instead of throwing.
  • In general WordPress developers are more comfortable returning error objects than throwing.
  • most importantly: it gives me a justification to leave the return type off the method signature (since php7.4 doesn't support union return types) since I didn't want to argue about why a legacy project wanting to migrate to this API but keep their existing DTO object isn't a justifiable use case for polymorphic tech debt at this early stage. 😅

(I drafted this before you both aligned yesterday on #53 and only saw after I pushed, so in my head this was still very much a proposal ).

My takeaway from this conversation is that we're good not caring about that last point (between overloading and the filter if someone really, realy thinks shimming a DTO or VO into this api is a good idea, they have ways), so if y'all don't think it's outweighed by the first two (I don't), I'll use prepare_*( array<string,mixed> ): array<valid-shape> and just throw

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

* ...<string, mixed>,
* } $properties
*/
protected function validate_properties( array $properties ) {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The only changes here are that they're now InvalidArgumentExceptions() instead of _doing_it_wrong(). The conditionals and error message are identical to what was in WP_Abilities_API::register()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I double-checked with the implementation @felixarntz used in the demo plugin and it seems to be compatible even with the strict checks for madatory properties: label, description and execute callback.

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/326266fd62fc805ceac0dfd4fe71cd3cc3e7cad3/includes/Abilities/Abstract_Ability.php#L32-L48

Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
Comment threadincludes/abilities-api/class-wp-ability.php
@justlevine
justlevine marked this pull request as ready for review September 2, 2025 16:34
@github-actions

github-actionsBot commented Sep 2, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@justlevinejustlevine self-assigned this Sep 2, 2025
@gziologziolo added the [Type] Enhancement New feature or request label Sep 3, 2025

@gziologziolo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have just realized that $name is passed as a first argument so it won't be possible to make it optional with the intent that implementer extending WP_Abilities defines it similar to label, description, and the execute callback. That's perfectly fine. With the changes included, the code from the demo plugin will be simplified nicely:

wp_register_ability(
'wp-ai-sdk-chatbot-demo/get-post',
array(
- 'label' => __( 'Get Post', 'wp-ai-sdk-chatbot-demo' ),- 'description' => 'Mock description.',- 'execute_callback' => $mock_execute_callback,
'ability_class' => Get_Post_Ability::class,
)
);

@gziolo

Copy link
Copy Markdown
Member

Let's see what feedback @felixarntz has to share before landing these changes.

@felixarntzfelixarntz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@justlevine This looks great, thank you! I left a few comments on the open discussion points (I'm slightly favoring simply throwing exceptions and keeping the code as it is now), but I'll preemptively approve, since either way it's not a blocker.

@felixarntz

Copy link
Copy Markdown
Member

Once this is merged and available in a release, I'll update the demo plugin :)

@gziolo
gziolo enabled auto-merge (squash) September 5, 2025 07:03
@gziolo
gziolo merged commit 769de9e into WordPress:trunkSep 5, 2025
16 checks passed
@gziolo

Copy link
Copy Markdown
Member

As I start my day, I'll draft the refactoring described in #54 (comment). I landed this PR so all the essential changes are included in the next package release as soon as possible.

@justlevine

justlevine commented Sep 5, 2025

Copy link
Copy Markdown
ContributorAuthor

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

@justlevine
justlevine deleted the dev/WP_Ability-property-registration branch September 5, 2025 07:23
@gziolo

Copy link
Copy Markdown
Member

Oh, nice. I see you started the process of renaming to $args. I will employ some coding agent to finish it 😄

@gziolo

Copy link
Copy Markdown
Member

@gziolo feel free to lift https://github.com/justlevine/abilities-api/tree/dev/wp_ability-prepare_args I didnt have time to review it before passing out last night, but might save you a few minutes

I continue based on the commit from your branch in #59.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] EnhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve the properties validation when using ability_class during registration

4 participants

@justlevine@gziolo@felixarntz