-
Notifications
You must be signed in to change notification settings - Fork 26
dev!: require string $name
for registration and introduce ability_class
arg
#21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dev!: require string $name
for registration and introduce ability_class
arg
#21
Conversation
string $name
for registrationstring $name
for registration
It's a similar behavior to block types registry:
The documentation included promotes usage through |
To me this is a great example of what we want to avoid. Since it was merged in 5.0 without (as far as I can tell) a specific use case in mind, it's now irremovable tech debt. In contrast, look how the WP6.5
I'ts more than "promotes", the WP_Ability class is specifically marked as
Biggest problem is that this is a 6.9 core merge candidate so none of us really have time to anticipate problems before it becomes tech debt. It's a nonbreaking change to make "Smaller" problems:
tl;drSo really (and in light of the WP philosophy on decisions, not options) IMO the question is reversed: if this is only about conceptual flexibility and we don't have a specific use case in mind, then why complicate both the dx and the maintenance so early, and in an irreversible way |
Hey @justlevine ! You are right - backwards compatibility is both a biggest advantage of WP and its biggest problem.
The reason to allow for instantiating a preregistered Ability is to introduce the way to extend the behavior of WP_Ability without merging entire API to do to. It's opening the door slightly so that more advanced use-cases are not yet supported by the api. Let me give you a simplified example of what we need. This is a tool that is currently used in some capacity on production and I would love to rewrite it to Abilities API. It is meant to let the model rewrite content in specific gutenberg blocks on a page and it does call openai on its own
Keep in mind this is simplified, but what we found is that simple abilities are not enough. because
Now technically this could have been all achieved by having a seperate class tied into wp_ability with hooks using its methods and I could keep this complexity "on the side" of wp_ability. I, as a developer could do that but:
What we found is that these complex abilities are needed sooner than we expected - a simple clean stateless ability sounds nice in theory but in real production use cases this extendability is needed pretty soon. Every way that I could think of was working against this API introducing even more technical debt and lifecycle problems and this seemed like the cleanest - it at least acknowledges the need and channels these advanced use cases into one place. I would hate to see community to come up with for example global variables to manage state in abilites. |
Hey @artpi thanks so much for the considered and detailed response 🤩 I want to rubberduck a few different approaches (including just some internal polish on the polymorphic approach) using the trimmed down example and will try and get back here by my EOD with a real reply. In the interim some quick notes (mainly so I don't forget while I'm AFK),
|
I'm not sure I fully understand the problem here. You pass either a ability name ( I think the root of the problem is method overloading (same method name, different parameter signatures) here which is a standard feature in many languages like Java, C++, C#, or even it is supported in TypeScript. I don't know how far we can go with it in PHP, and the existing tooling. However, I can think of some ways how to mitigate the potential challenges for extenders:
We pass the instance directly after very light verification of the name: abilities-api/includes/abilities-api/class-wp-abilities-registry.php Lines 66 to 97 in f36c71f
For now, I would prefer to focus on the hooks that optimize for 3rd party usage like plugins, so they could modify things like
I'm also thinking about a filter that allows removing some of the abilities when fetching the full list from the registry, in the case when site owner would like to filter something out in a given context.
That might work. We could offer an
Right, the In this case, we should not mark it internal, because devs need to use the public API like
I think it's mostly the question how this is integrated with tooling and how it's documented for devs so they could benefit from it. I wouldn't like if we limit the creativity of developers or don't respect their design choices. At the same time, as @justlevine noted, we don't want to run into backward compatibility lock so we can't evolve the API as we see need for new use cases. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trunk #21 +/- ##
========================================
Coverage ? 89.82%
Complexity ? 94
========================================
Files ? 7
Lines ? 511
Branches ? 0
========================================
Hits ? 459
Misses ? 52
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Hey folks, sorry it took longer to get back here than expected but I wanted to make sure I really worked through it all (and confirmed how much refactoring @artpi 's example would minimally require to work with the existing
Yeah, that's the problem IMO. It means we cant rely that the things masquerading as a WP_Ability inside the registry have all the properties the registry is supposed to guarantee. Which is why:
Unless I misunderstood @artpi ("without merging entire API to do so") this isn't about tooling, its about being about being able to minimize the migration requirements in order to get preexisting AI tools onto WP_Ability_Registry. This sort of polymorphism IMO treats (and forces us to treat) WP_Ability as an interface, and means that neither extenders/tools in the chain, other parts of core, or even WP_Ability itself wouldn't be able to rely on
Both of these severely limit any future evolution of APIs, so if the Chesterton Fence around current WPCore code patterns isnt enough to deter this, please at least consider that. In the last commit I add 39bf7c9 which adds the With it, @artpi 's example would go from add_action( 'abilities_api_init', static function() {
// ... did some stuff to justify this.
$tool_instance = new Content_Rewrite_Tool(); // if we're lucky. If this is before 'abilities_api_init' the possible hook order is even less stable.
wp_register_ability( $tool_instance );
} ); to add_filter( 'wp_ability_class', static function( string $class_name, string $ability_name ) {
return 'wpcom/rewrite_content' === $ability_name ? Content_Rewrite_Tool::class : $class_name;
, 10, 2 );
add_action( 'abilities_api_init, static function() {
// maybe do some stuff, maybe not. our API is immune.
wp_register_ability( Content_Rewrite_Tool::NAME, Content_Rewrite_Tool::PROPS ); // or however they refactor to minimally comply with WP_Ability's public signature.
} In other words, for just 2 extra LOC (and IMO likely more saved elsewhere), we can support the exact same use case without the other downsides. Plus, core actually has a way to deprecate hooks, unlike the first parameter of a global function or public method. (Not that I think this will get deprecated. It's an existing and modern core pattern, clean and performant, and provides an easy path forward to experiment with and eventually core support Ability Types, Ability Chains, Nested Abilities, etc without any of the extra baggage). Separately wanted to confirm that the rest of concerns / needs raised can be done by passing variables to the different closures on E.g. wp_register_ability(
'wpcom/rewrite_content',
[
...$other_args,
'execute_callback' => function( array $input ) {
$user_request = $input['userRequest'] ?? '';
$block_client_id = $input['blockClientId'] ?? '';
// Calling a function from a class.
$blocks_to_rewrite = $this->get_blocks_to_rewrite( $block_client_id, $page_content );
// calling a singleton method.
$system_prompt = \WPCOM\AI\Context::process(
$this->context,
self::REWRITE_CONTENT_PROMPT
);
try {
require_lib( 'openai' );
$openai = new \OpenAI( $this->agent->feature ?? 'big-sky' );
return $openai->doSomething();
} catch ( \Throwable $e ) {
return new WP_Error(
'my-code'
$e->getMessage()
);
},
]
); @artpi please double check me with your IRL tool, but from the example provided your short-term migration path would likely be shorter this way than trying to conform with |
Hey @justlevine , Im definitely open to those and i havent had the time to digest it properly, but here is some early feedback:
The idea of a filter that whitelists a class is fine definitely, but in this example we have a props problem: Regarding this example:
The problem here is state. With classes, consumers of abilities can ammend them during execution context adding stateful things like I'll think of how to make it work with your concerns, but come to think of it, my reasons for pushing for extending WP_Ability are:
|
Thanks @artpi !
Custom props can still be passed, they would just be passed via a custom key on the class Content_Rewrite_Tool extends \WP_Ability {
private ?MyStateDTO $state;
private array $some_other_complex_prop;
// other WP_Ability overrides.
}
...
wp_register_ability( 'wpcom/rewrite_content', [
...$otherProperties,
'state' => $my_state_or_whatever,
'some_other_complex_prop' => $nested_array_or_whatever
] ); (Technically you could even
Not sure I understand this: is
To clarify, I don't believe we ever disagreed about the importance of being able to extend
All of this is possible with the current PR and straightforward with this API (either I'm happy to keep pseudocoding if you think of some other examples to try while you're reviewing - it's good prior art both for the docs, and the soon to commence experiments work. |
As we're getting closer to cutting an initial release, please remember that even if this gets merged, we can continue the conversation and restore the polymorphic Still here and happy to assuage any additional concerns people make have. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. I'm fine with starting with the strict approach that still allows to use a custom implementation of class extending WP_Ability
.
I took an attempt to see how the example provided by @artpi woulf fit. It looks like the following would be possible with the changes proposed: class Content_Rewrite_Tool extends WP_Ability {
public array $parameters;
public function __construct( string $name, array $properties ) {
parent::_construct( $name, $properties );
$this->parameters = [
'userRequest' => [
'type' => 'string',
'description' => 'The user\'s request describing how to rewrite the content.',
],
'blockClientId' => [
'type' => 'string',
'description' => 'The client ID of the block containing the content to rewrite.',
],
'followUpTasks' => [
'type' => 'boolean',
'description' => 'Set to true if the user request has to be broken into multiple steps and other tasks remain to be done after this one.',
],
];
}
// All other methods.
}
$result = wp_register_ability(
'wpcom/rewrite_content,
array(
'label' => 'rewrite_content',
'description' => 'Rewrite content to match a user request. If a user requests a change in tone but doesn\'t provide a specific tone, ask for clarification.',
'execute_callback' => array( Content_Rewrite_Tool::class, 'invoke' ),
'ability_class' => Content_Rewrite_Tool::class,
)
);
|
string $name
for registrationstring $name
for registration and introduce ability_class
arg
What
This PR changes both
wp_register_ability()
andWP_Ability_Registry::register()
to only take astring $name
, instead of either a name or a pre-instantiatedWP_Ability
.Also removed the
"Optional"
text$properties
param doc - since they aren't in fact optional, they just silently error with a_doing_it_wrong()
.(Note: I'm just code-reviewing and leaving feedback. If you think this warrants a wider discussion I can open a matching issue, otherwise I think a discussion here following by [accept | reject] is enough)
Why
Per the rest of the inline docs, neither
WP_Abilities_API
orWP_Ability
are meant to be called directly, so there isn't really a scenario wherewp_register_ability()
should be called with a pre-instantiatedWP_Ability()
.Keeping the API intentionally limited makes the potential for breaking API changes (and unintentional misuse/adoption), making both our and users lives easier.