Skip to content

Conversation

justlevine
Copy link
Contributor

What

This PR fixes the @phpstan-param type annotations for the required label, description, execute_callback, and permission_callback $args on wp_register_ability()

This PR was authored by @johnbillion on justlevine#6, but I didn't see it before the upstream PR got merged. All I did was cherrypick it onto truck.

Why

Per John

We can help PHPStan users by making these elements required. It doesn't affect the guard conditions in WP_Ability::prepare_properties() because the args array isn't documented there.

Copy link

github-actions bot commented Sep 30, 2025

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: johnbillion <johnbillion@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>

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

@justlevine justlevine requested a review from Copilot September 30, 2025 23:11
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes PHPStan type annotations for the wp_register_ability() function by marking the label, description, execute_callback, and permission_callback parameters as required instead of optional in the $args array.

  • Updates PHPStan parameter type annotations to reflect required fields
  • Improves static analysis accuracy for developers using PHPStan
  • Maintains existing runtime validation while providing better IDE/tooling support

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

Copy link

codecov bot commented Sep 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.69%. Comparing base (8e0b08a) to head (aab9b74).
⚠️ Report is 1 commits behind head on trunk.

Additional details and impacted files
@@            Coverage Diff            @@
##              trunk      #97   +/-   ##
=========================================
  Coverage     85.69%   85.69%           
  Complexity      103      103           
=========================================
  Files            16       16           
  Lines           776      776           
  Branches         86       86           
=========================================
  Hits            665      665           
  Misses          111      111           
Flag Coverage Δ
javascript 92.66% <ø> (ø)
unit 82.97% <ø> (ø)

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.

@gziolo
Copy link
Member

gziolo commented Oct 1, 2025

This PR fixes the @phpstan-param type annotations for the required label, description, execute_callback, and permission_callback $args on wp_register_ability()

Overall, I'm in favor of making these args required with a small remark. There are possible paths where the developer might omit these args, for example when using ability_class. See usage in WC US demo plugin from @felixarntz:

https://github.com/felixarntz/wp-ai-sdk-chatbot-demo/blob/4e0e2ed5db5adb2f259659f85e0a1ce997daecc8/includes/Abilities/Abilities_Registrar.php#L26-L32

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

An important note, the developer still must provide these properties, which is satisfied with this code:

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

Another, edge case scenario that I can think of would be with the new WP filter register_ability_args.

Could we use a union like the one below, or would it make it too complicated?

/**
 * @phpstan-param
 *   (array{
 *     label: string,
 *     description: string,
 *     execute_callback: callable(mixed $input=): (mixed|\WP_Error),
 *     permission_callback: callable(mixed $input=): (bool|\WP_Error),
 *     input_schema?: array<string,mixed>,
 *     output_schema?: array<string,mixed>,
 *     meta?: array<string,mixed>,
 *     ability_class?: class-string<\WP_Ability>,
 *     ...<string, mixed>
 *   }
 *   |
 *   array{
 *     ability_class: class-string<\WP_Ability>,
 *     label?: string,
 *     description?: string,
 *     execute_callback?: callable(mixed $input=): (mixed|\WP_Error),
 *     permission_callback?: callable(mixed $input=): (bool|\WP_Error),
 *     input_schema?: array<string,mixed>,
 *     output_schema?: array<string,mixed>,
 *     meta?: array<string,mixed>,
 *     ...<string, mixed>
 *   })
 *   $args
 */

Perhaps we could also include a unit test that illustrates the usage observed in the WC US demo plugin, providing internal PHPStan verification.

@gziolo gziolo requested a review from felixarntz October 1, 2025 07:16
@gziolo gziolo added the [Type] Developer Documentation Improvements or additions to documentation label Oct 2, 2025
@felixarntz
Copy link
Member

+1 to what @gziolo suggested above: Neither the current nor the suggested shape in this PR are ideal IMO, because these arguments are only required if ability_class is not provided.

Something like this union approach would address the actual requirements properly.

@justlevine
Copy link
Contributor Author

because these arguments are only required if ability_class is not provided.

@felixarntz nit: they are only not required if ability_class is provided and the WP_Ability child class overloads the default validation

The fact that WP_Ability can be overloaded so none of the typed properties is an implementation detail from #21 that is there to allow forpre v0.1.0 and in sometimes even pre-Core AI team projects to have an easy adoption path. It's not an endorsement.

IMO as I've said elsewhere, removing the predictability of required args is a huge footgun in general, but way worse to introduce into an initial release in v6.9 where it becomes permanent tech debt

In the interim a downstream dev (using PHPStan level 8) really thinks there's a reason for their polymorphism, then I imo a one-line // @phpstan-ignore argument.type is an encouraging bit of friction to have.
Especially at this early stage where the only prior art there really is right now are ports like Felix's demo.

@felixarntz
Copy link
Member

@justlevine

nit: they are only not required if ability_class is provided and the WP_Ability child class overloads the default validation

Fair point.

The fact that WP_Ability can be overloaded so none of the typed properties is an implementation detail from #21 that is there to allow forpre v0.1.0 and in sometimes even pre-Core AI team projects to have an easy adoption path. It's not an endorsement.

Where was that decided? I disagree with that assessment. Extending WP_Ability IMO is and should be a totally reasonable way to use this API.

@gziolo
Copy link
Member

gziolo commented Oct 3, 2025

Where was that decided? I disagree with that assessment. Extending WP_Ability IMO is and should be a totally reasonable way to use this API.

Agreed that extending WP_Ability is going to be essential for adoption in larger plugins, as they often have to find the best approach to fit their system. How they do it should be left to their discretion. Example from Woo:

https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Internal/Abilities/REST/RestAbility.php#L12-L37

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

In the interim a downstream dev (using PHPStan level 8) really thinks there's a reason for their polymorphism, then I imo a one-line // @phpstan-ignore argument.type is an encouraging bit of friction to have.

I tend to agree we can optimize for tha majority of devs using the default way to register abilities 👍🏻

@justlevine
Copy link
Contributor Author

justlevine commented Oct 3, 2025

The fact that WP_Ability can be overloaded so none of the typed properties is an implementation detail from #21 that is there to allow forpre v0.1.0 and in sometimes even pre-Core AI team projects to have an easy adoption path. It's not an endorsement.

Where was that decided? I disagree with that assessment. Extending WP_Ability IMO is and should be a totally reasonable way to use this API.

@felixarntz @gziolo not the ability to extend the class altogether, the ability to make those required args optional when extending (unlike Woo in that last class which a. respects the shape and b. doesn't need a class even 'output_schema' => '__return_true' would have been the same).

Iirc the discussion started on #21 and moved to #53 + #54. The broader argument against polymorphism also came up in #61 and some other tangental PRs.

@felixarntz
Copy link
Member

Right, I know we talked about it in several places, but I don't recall any decision that this was discouraged / not recommended or anything along these lines. If you implement your own ability_class, you can do pretty much whatever you're want, so IMO it's a "do it at your own risk" rather than "don't do it". Which arguments are required in wp_register_ability should be dependent on what happens in WP_Ability (or the custom ability_class), except for the few things that logic in wp_register_ability itself needs. Everything else, for the sake of that function, needs to be optional when a custom ability_class is provided, since only that class can decide what is required.

When no ability_class is provided, we can mark it everything as required as we have full control over the code in WP_Ability. FWIW, this is going to be 95% of usage most likely, so I don't think there is a realistic "decline in DX" by not requiring the parameters when a custom ability_class is provided.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

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

Based on the conversation so far, I continue to question this approach: What is the concrete argument against going with @gziolo's suggestion from #97 (comment) instead?

@justlevine
Copy link
Contributor Author

Right, I know we talked about it in several places, but I don't recall any decision that this was discouraged / not recommended or anything along these lines.

Correct. No "decision" (if I'm now understanding your emphasis) has been made about actively promoting polymorphism in the initial core merge of this API. To make progress without such a decision we've been mostly avoiding polymorphism whenever it would be a breaking change to revert (because going from a single type to a union is a nonbreaking).

So when I say that #21 isn't an endorsement on polymorphism, I mean it was explicitly merged without a decision to endorse polymorphism having been made.

If you implement your own ability_class, you can do pretty much whatever you're want, so IMO it's a "do it at your own risk" rather than "don't do it".[...]

WordPress documentation has always been prescriptive, not descriptive. It's why most types in the codebase aren't unioned to mixed. A // @phpstan-ignore (vs a fatal error if not intentionally overloaded), feels very much like "do it at your own risk".

When no ability_class is provided, we can mark it everything as required as we have full control over the code in WP_Ability. FWIW, this is going to be 95% of usage most likely, so I don't think there is a realistic "decline in DX" by not requiring the parameters when a custom ability_class is provided.

The correlary here is that for <5% usage we're introducing a problematic footgun into an API we're navelgazing into existence isolated from core review, and where forward incompatible changes remain as permanent tech debt. We can always explicitly document the polymorphism in the future if/when we decide that it's something we want holistically and have it be nonbreaking.

What is the concrete argument against going with @gziolo's suggestion from #97 (comment) instead?

If you mean "Overall, I'm in favor of making these args required with a small remark.", then yes that's the approach I think we should take ;)

If you mean "Could we use a union like the one below, or would it make it too complicated?" then a quick tl;Dr would be:

  • polymorphism is bad for APIs. In a codebase that doesn't allow for breaking changes, the risk from polymorphism (and the costs of working around it) are higher.
  • this particular brand of polymorphism already affects the reliability and DX of the existing hooks. It will also likely get in the way of any Abstract_Ability/ Abstract_REST_Controller pattern that would holistically surface once we start implementing abilities in core.
  • The non-union approach still allows for polymorphism without actively promoting it. At most it requires a one-line annotation, which is arguably good friction to have. All of this follows how WordPress core traditionally handles polymorphism that isn't actively recommended.
  • we can iterate from a non-union approach to a union approach in the future without it being a breaking change. We cannot go vice versa.

@felixarntz what do you believe are the reasons:

  1. To introduce polymorphism altogether at such an early stage to the API
  2. To actively support and document polymorphism here (and make it a breaking change if we decide we need to require those args in the future )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Developer Documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants