- Notifications
You must be signed in to change notification settings - Fork 46
Add serialization methods to WP_Ability and WP_Ability_Category #109
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
base: trunk
Are you sure you want to change the base?
Add serialization methods to WP_Ability and WP_Ability_Category #109
Conversation
- Add WP_Ability::to_array() method for declarative array export - Add WP_Ability::to_json_schema() method for JSON Schema generation - Add comprehensive test coverage for both methods - Ensure annotations and show_in_rest remain under meta structure The to_array() method returns a complete array representation of the ability including name, label, description, schemas, and metadata, excluding callbacks as they are not serializable. The to_json_schema() method generates a JSON Schema Draft 7 compliant schema describing the ability's structure, useful for validation in both PHP and JavaScript contexts. Implements feedback from PR WordPress#108.
Replace direct property access with public getter methods in to_array() and to_json_schema() methods to follow encapsulation best practices.
Add wp_ability_{name}_to_array and wp_ability_{name}_to_json_schema filters to allow developers to modify the output of both methods. - Add filter documentation with @SInCE tags - Pass ability instance as second parameter to filters - Add comprehensive test coverage for filter functionality - Verify filters receive correct parameters and modify output | 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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@ ## trunk #109 +/- ## ============================================= + Coverage 80.46% 94.61% +14.14% ============================================= Files 20 7 -13 Lines 1474 334 -1140 Branches 119 119 ============================================= - Hits 1186 316 -870 + Misses 288 18 -270
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:
|
Add JsonSerializable interface to allow abilities to be passed directly to json_encode() without manually calling to_array(). - Implement jsonSerialize() method that delegates to to_array() - Automatically applies wp_ability_{name}_to_array filter - Add 4 comprehensive tests for JsonSerializable functionality - Verify json_encode() works correctly with WP_Ability instances Addresses feedback from @westonruter in PR WordPress#109.
gziolo left a comment
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.
Thank you for opening this proposal.
I see some value in introducing ::to_array. I left some feedback regarding the proposed implementation. I wanted to note that the description of this PR mentions applicability to REST API, so I was wondering why the REST API controller wasn't updated to use the new API:
abilities-api/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php
Lines 192 to 199 in 4dc57b3
| $data = array( | |
| 'name' => $ability->get_name(), | |
| 'label' => $ability->get_label(), | |
| 'description' => $ability->get_description(), | |
| 'input_schema' => $ability->get_input_schema(), | |
| 'output_schema' => $ability->get_output_schema(), | |
| 'meta' => $ability->get_meta(), | |
| ); |
Aside: I see that we still need to account for non-serializable data in meta in the exisitng REST API implementaiton, too.
Regarding the to_json_schema proposal. I'm a bit more skeptical it's useful on the WP_Ability class level. There is an existing schema in place in the REST API endpoint:
abilities-api/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php
Lines 235 to 287 in 4dc57b3
| public function get_item_schema(): array { | |
| $schema = array( | |
| '$schema' => 'http://json-schema.org/draft-04/schema#', | |
| 'title' => 'ability', | |
| 'type' => 'object', | |
| 'properties' => array( | |
| 'name' => array( | |
| 'description' => __( 'Unique identifier for the ability.' ), | |
| 'type' => 'string', | |
| 'context' => array( 'view', 'edit', 'embed' ), | |
| 'readonly' => true, | |
| ), | |
| 'label' => array( | |
| 'description' => __( 'Display label for the ability.' ), | |
| 'type' => 'string', | |
| 'context' => array( 'view', 'edit', 'embed' ), | |
| 'readonly' => true, | |
| ), | |
| 'description' => array( | |
| 'description' => __( 'Description of the ability.' ), | |
| 'type' => 'string', | |
| 'context' => array( 'view', 'edit' ), | |
| 'readonly' => true, | |
| ), | |
| 'input_schema' => array( | |
| 'description' => __( 'JSON Schema for the ability input.' ), | |
| 'type' => 'object', | |
| 'context' => array( 'view', 'edit' ), | |
| 'readonly' => true, | |
| ), | |
| 'output_schema' => array( | |
| 'description' => __( 'JSON Schema for the ability output.' ), | |
| 'type' => 'object', | |
| 'context' => array( 'view', 'edit' ), | |
| 'readonly' => true, | |
| ), | |
| 'annotations' => array( | |
| 'description' => __( 'Annotations for the ability.' ), | |
| 'type' => 'object', | |
| 'context' => array( 'view', 'edit' ), | |
| 'readonly' => true, | |
| ), | |
| 'meta' => array( | |
| 'description' => __( 'Meta information about the ability.' ), | |
| 'type' => 'object', | |
| 'context' => array( 'view', 'edit' ), | |
| 'readonly' => true, | |
| ), | |
| ), | |
| 'required' => array( 'name', 'label', 'description' ), | |
| ); | |
| return $this->add_additional_fields_schema( $schema ); |
WordPress is using its flavor of JSON Schema Draft 4 with some non-standard properties:
https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#json-schema-basics
What would be the practical usage of JSON Schema draft 7 included on the WP_Ability level? I don't see it applicable inside WordPress core given the explanation above.
| 'description' => $this->get_description(), | ||
| 'input_schema' => $this->get_input_schema(), | ||
| 'output_schema' => $this->get_output_schema(), | ||
| 'meta' => $this->get_meta(), |
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.
Here, to make the PHPDoc correct, you would have to account for the callback in any custom meta provided or limit that object to annotations and show_in_rest.
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.
Good point I will change the docblock, as I feel like the to_array() should allow other meta.
| * @param array<string,mixed> $array The ability as an associative array. | ||
| * @param \WP_Ability $ability The ability instance. | ||
| */ | ||
| return apply_filters( "wp_ability_{$this->get_name()}_to_array", $array, $this ); |
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.
Some example from WP core:
I don't see filters used there. @justlevine shared some concerns regarding using filters on methods that are executed multiple times in #37 (comment). More broadly, I'm curious what scenarios this helper method would require these filters, taking into account that the consumer can still manipulate the result however they like.
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.
Filtering here allows individual modification of the "context" for the AI models later on, which is not required for the other methods like this one
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.
Can you share an example of how this ::to_array() would be consumed to pass to the AI model? It isn't used this way in the codebase currently, so I might be missing some nuance here.
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.
@gziolo thanks for tagging me 🙇
IMO semantically it would make sense that a filter on to_array() would trigger every time a WP_Ability is transformed, so I don't have that same concern here.
I also like @bordoni 's use case, since that sort of tailoring wouldn't really make sense extending the class to overload this function. (Though would like to know more as to what modifications you would want injected at that point in the lifecycle and not earlier).
That said, I do think it's perfectly fine for WordPress 6.9 to not allow devs to inject top-level properties via a hook. They can already filter args, and dump whatever they want in meta, so another filter feels like something we can do additively in 7.0 and later.
(Per previous decisions we can merge now and decide whether to strip it during beta).
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.
When wanting to keep structures concrete, filters are not a great tool :)
It's a problem that's discussed over and over, every once in a while it comes up, but obviously, in a filter callback one can do anything one likes. I am not opposed to adding a filter here, but I am much more of a proponent of adding one based on concrete needs.
In WordPress Core, every API interface you add will have to be there more or less forever. You can't remove it. But you can always add something. So unless there's a critical need we know of ahead of time, I'd always vote for most minimal public API surfaces possible.
If we still want to proceed with adding a filter here right away, I'd strongly suggest to not have it run on the entire data. The use-case mentioned is to inject additional key-value pairs - that's totally reasonable and can be done by filtering an empty array, then merging it into the base result. This way, we allow adding stuff, but we don't allow messing up the base result, which is critical to ensure data integrity.
Alternatively, to achieve the same goal, create a copy of $array first, then filter the copy (this way the filter retains access to the whole associative array and can operate on it), and after the filter application merge the original array back into the copy (to ensure the original data is still the same). The filter docs would then need to mention that this can only be used to add additional keys, not to change existing keys.
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.
Is there already an existing use case for the filters, or is this a case of "we expect people will want this"? It's not a popular opinion in the WP community, but I'm a big fan of not adding filters to early versions of things that don't have a lot of use yet. It's early days and we may need to change things we can't realize yet. In other words, I'm a fan of code that's easy to change, at this stage of development.
Like @felixarntz says above, every API interface you add basically has to remain forever, unless/until it's deprecated and replaced by something else. In this case, we have PHP classes, methods, and filters, all of which are public and a form of an API, and all of which are harder to change once they're in the wild.
If there are already solid needs for the filters, then the additive approach mentioned by Felix seems the better way to go.
| @gziolo I can adjust to use the JSON Schema Draft 4, my goal with this PR was to address the idea that you could look and potentially modify the I can decouple the |
| The challenge is that if we even update |
| Thanks so much for this, @bordoni! Since this is based off my comment, I'll elaborate on my intent with this: We know that abilities will be represented both in memory (PHP) as well as in JSON — input schema, output schemas (e.g. REST), and for use in JS. Having a consolidated way for this to be represented in each context is useful. We actually did this with all the many DTOs in the PHP AI client. We created two Interfaces every DTO has: We also used With that in mind, I felt like it could be a good use case for Abilities and Ability Categories (#102), since we know it will be used in a number of contexts, and keeping that all in sync could be a bear. Finally, with regards to v4 vs v7 JSON schema, my inclination is to lean on v4 since REST relies on it (😭), but if v7 is necessary, then I'd make a translation layer for REST (even if it's just a |
You're definitely right, here. The schema support in the REST API is... not ideal. Hahah! I'm really wondering if having a I also wouldn't generically have the REST API pull this in, so the implementing code can also play a role in making this work properly. |
…-and-to-json-schema
- Implement JsonSerializable interface for WP_Ability_Category - Add to_array() method returning slug, label, description, and meta - Add wp_ability_category_{slug}_to_array filter - Add 9 comprehensive tests covering array conversion and JSON serialization Maintains consistency with WP_Ability implementation pattern. - Change schema version from Draft 7 to Draft 4 for WP REST API compatibility - Replace `const` keyword with `enum` array (Draft 4 doesn't support const) - Update test assertions to check for enum instead of const - Update docblock to reflect Draft 4 compliance All 100 tests passing with new schema format.
| @gziolo @JasonTheAdams Modified to use Draft 4 to match the WordPress default, we can talk about others later on. I also got the |
to_array() and to_json_schema() methods to WP_Ability| I'm curious regarding JSON Schema v4 vs v7, what are the concrete differences that affect us most in WordPress? To my knowledge (not being very familiar with the spec), an important distinction is having a While I would like to move WordPress to v7, I think this is not a realistic goal in anywhere near-term. Backward compatibility is crucial, and more or less every REST API endpoint schema and arguments definition would need to be modified if we made the switch. So I wouldn't bet on that anytime soon, we need to align with WordPress's status quo while making adaption with other paradigms easy. |
The main quirk that always surprises me in the REST schema is that each property is evaluated on its own during validation. That is, when validating a given property within the schema, only that property's part of the schema is inspected, not the whole schema object. This means that you cannot reference multiple properties at once using something like a |
| As we are getting feedback sorted out, I'd like to better understand whether the intention was also to refactor existing usage in the codebase, or if this is purely for the convenience of extenders? I shared code references in #109 (review). |
| I would be more than glad to setup a secondary PR or do it here the refactor of the existing stuff to use this new structure. Just wanted to get approval on if this part looks good. (huge PRs can be such a pain to review) |
…re/add-to-array-and-to-json-schema
This PR adds serialization methods to both
WP_AbilityandWP_Ability_Categoryclasses to provide declarative ways to export data for use in REST APIs, JavaScript contexts, and schema validation.Changes
WP_Ability Methods
WP_Ability::to_array()Returns a complete array representation of the ability including:
name- The ability name with namespacelabel- Human-readable labeldescription- Detailed descriptioninput_schema- Input validation schema (if defined)output_schema- Output validation schema (if defined)meta- Metadata includingannotationsandshow_in_restCallbacks (
execute_callback,permission_callback) are excluded as they are not serializable.Use cases:
WP_Ability::to_json_schema()Returns a JSON Schema Draft 4 compliant schema describing the ability's structure. The schema includes:
enumarray for the ability name (Draft 4 compatible)Note: Uses JSON Schema Draft 4 to maintain compatibility with WordPress REST API standards.
Use cases:
WP_Ability::jsonSerialize()Implements the
JsonSerializableinterface, allowing abilities to be passed directly tojson_encode().WP_Ability_Category Methods
WP_Ability_Category::to_array()Returns a complete array representation of the category including:
slug- The unique category sluglabel- Human-readable labeldescription- Detailed descriptionmeta- Optional metadataUse cases:
WP_Ability_Category::jsonSerialize()Implements the
JsonSerializableinterface for direct JSON encoding support.Filters
Both classes include filters to allow developers to modify the output:
WP_Ability Filters:
wp_ability_{name}_to_array- Filters the array representation@param array $array- The ability as an associative array@param \WP_Ability $ability- The ability instancewp_ability_{name}_to_json_schema- Filters the JSON Schema representation@param array $schema- The JSON Schema representation@param \WP_Ability $ability- The ability instanceWP_Ability_Category Filters:
wp_ability_category_{slug}_to_array- Filters the category array representation@param array $array- The category as an associative array@param \WP_Ability_Category $category- The category instanceTest Coverage
Added comprehensive test coverage:
WP_Abilityserialization (to_array, to_json_schema, JsonSerializable)WP_Ability_Categoryserialization (to_array, JsonSerializable)Implementation Details
JsonSerializableinterface on both classesannotationsandshow_in_restundermetastructure as per existing conventionsExample Usage
Addresses
Implements feedback from @JasonTheAdams in PR #108