WP_Post_Type and WP_Taxonomy: describe the shape of the labels and capability objects - #477
Draft
swissspidy wants to merge 5 commits into
Draft
Conversation
`WP_Post_Type::$labels`, `WP_Post_Type::$cap`, `WP_Taxonomy::$labels` and `WP_Taxonomy::$cap` are declared as plain `stdClass`, so every label and capability read off them is `mixed`. The three capability maps are `bool[]`, which leaves their keys as `array-key`. The label and capability objects are built by `get_post_type_labels()`, `get_post_type_capabilities()`, `get_taxonomy_labels()` and `WP_Taxonomy::set_props()`, all of which fill in every default, so their contents can be described exactly: - Labels whose default is `null` for one of the two hierarchies, such as `popular_items` on a hierarchical taxonomy, are `string|null`. - `menu_name` and `name_admin_bar` are always present, even though `get_taxonomy_labels()` does not list them. Core reads `menu_name` off a taxonomy in `wp-admin/menu.php`. - The six capabilities that `get_post_type_capabilities()` only adds under `map_meta_cap`, and the conditional `template_name` post type label, are listed as required rather than optional: `map_meta_cap` ends up true for the default capability type, and an optional key would turn every direct read into an "undefined property" error. Note that these object shapes are sealed, so a custom label or capability registered on top of the defaults now needs its own annotation. That already applies to `get_post_type_capabilities()` and `get_taxonomy_labels()`, whose returns are described the same way. The four property entries can be dropped once WordPress ships the matching docblocks and the stubs are regenerated from that release, in the same way `WP_Theme::get()` was removed from the map. Refs szepeviktor/phpstan-wordpress#274 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122HqysjDiq2jSHebbSSu6A
swissspidy
force-pushed
the
claude/phpstan-wordpress-typing-f9zirv
branch
from
August 21, 2026 09:20
784e653 to
2230ad7
Compare
Member
|
You brought your robobuddy with you? |
WP_Query::query() and WP_Term_Query::query()WP_Post_Type and WP_Taxonomy: describe the shape of the labels and capability objects
WordPress documents the contents of an object like `WP_Post_Type::$labels` on the function that builds it, and refers to that function from the property with `@see`. The visitor already follows that convention for `@param`, through `discoverInheritedArgs()`, but not for `@var`, so a property stayed `stdClass` however well the function it points at was documented. Describing those properties in core therefore meant repeating a thirty-key list on the property, which is exactly what the `@see` exists to avoid. `@var` now follows the same reference. A property inherits the `@phpstan-return` shape of a function it `@see`s when the two types agree, so `object` shapes only land on `object` properties and `array` shapes on `array` ones. A shape of the property's own always wins, whether it comes from hash notation in the source or from the function map. Generating the stubs for WordPress 7.0.1 before and after produces an identical file: the four properties this was written for are still covered by their function map entries, which take precedence, and nothing else in core both carries a matching `@see` and lacks a shape of its own. Refs szepeviktor/phpstan-wordpress#274 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122HqysjDiq2jSHebbSSu6A
php-stubs#478 added function map entries at the same spot in the file, between `WP_REST_Request::set_param` and `WP_Theme`. Both sides are kept, in the alphabetical order the file uses. Its Composer and PHPCS changes are the same ones this branch already carried, so those files came out identical to upstream. `wordpress-stubs.php` merged cleanly, and regenerating it from the WordPress 7.0.1 tree reproduces the merged file exactly, apart from the header and `$wpdb` shim that generate.sh appends afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122HqysjDiq2jSHebbSSu6A
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
WP_Post_Type::$labels,WP_Post_Type::$cap,WP_Taxonomy::$labelsandWP_Taxonomy::$capare declared as plainstdClass, so every label and capability read off them ismixed.WP_User::$caps,WP_User::$allcapsandWP_Role::$capabilitiesarebool[], which leaves their keys asarray-keyrather than the capability name.These objects are built by
get_post_type_labels(),get_post_type_capabilities(),get_taxonomy_labels()andWP_Taxonomy::set_props(), all of which fill in every default, so their contents can be described exactly.Notes on the shapes
nullfor one of the two hierarchies, as recorded inWP_Post_Type::get_default_labels()andWP_Taxonomy::get_default_labels(), are typedstring|null. For examplepopular_itemsisnullon a hierarchical taxonomy, andparent_item_colonisnullon a non-hierarchical post type. Typing themstringmakes a legitimatenull === $tax->labels->popular_itemscheck report as always false.menu_nameandname_admin_barare always present on both objects, even thoughget_taxonomy_labels()documents neither andget_post_type_labels()documents onlymenu_name._get_custom_object_labels()setsname_admin_barunconditionally, andmenu_nameis added to the defaults by both label builders. Core reads$taxonomy->labels->menu_nameinwp-admin/menu.php.get_post_type_capabilities()only adds undermap_meta_cap, and the conditionaltemplate_namepost type label, are listed as required rather than optional.map_meta_capresolves totruefor the default capability type (WP_Post_Type::set_props()), so they are present in practice, and an optional key in an object shape turns every direct read into an "undefined property" error.Trade-off
Object shapes are sealed, so a custom label or capability registered on top of the defaults now needs its own annotation. That is already the case for
get_post_type_capabilities()andget_taxonomy_labels(), whose return values are described the same way today.Follow-up
The four property entries can be dropped once WordPress ships matching docblocks and the stubs are regenerated from that release, in the same way
WP_Theme::get()was removed from the map in #475. A patch adding those docblocks to core is prepared separately; it also fixes the two undocumented labels and the nullability noted above.See szepeviktor/phpstan-wordpress#274