Skip to content

Users: Lazy load capability data for author display lookups - #14143

Open
spacedmonkey wants to merge 3 commits into
WordPress:trunkfrom
spacedmonkey:fix/lazy-load-capablity-6
Open

spacedmonkey wants to merge 3 commits into
WordPress:trunkfrom
spacedmonkey:fix/lazy-load-capablity-6

Conversation

@spacedmonkey

Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/58001

Summary

Second attempt at lazy loading user capabilities, after [60915] was reverted in [61037]. This version keeps caps, roles and allcaps public. It also makes lazy loading opt-in, so WP_User behaves as before unless code asks for the lazy version.

  • New get_authordata( $user_id ). It returns a WP_User whose capability data loads on first access. Display-only call sites use it:
    • $authordata in generate_postdata() and WP::register_globals()
    • the author archive queried object
    • get_the_author_meta(), get_the_modified_author(), get_author_posts_url(), wp_list_authors()
    • get_permalink() for %author% structures
  • WP_User::init( $data, $site_id, $short_init = false ). With $short_init, for_site() unsets the three properties, and the existing magic methods load them on first access.
    • __get() now returns by reference, so $user->roles[] = 'x' and $user->allcaps['foo'] = true keep working, even as the first access.
    • WP_User implements JsonSerializable and __serialize(), so wp_json_encode() and serialize() load the data first. Native unserialization restores the result, and payloads serialized before this change still unserialize correctly.
  • WP_User loads eagerly by default. new WP_User, get_userdata(), get_user_by() and the current user behave exactly as before, so get_object_vars(), array casts and print_r() still include caps, roles and allcaps. This addresses the concerns behind the revert in comments 39, 42, 43 and 46, and Peter's test from comment 53 is included.

Supersedes #13825. Unlike that PR, reading $authordata->roles directly loads the data instead of returning an empty array.

Behaviour changes (for the dev note)

These apply only to users returned by get_authordata():

  • get_object_vars(), (array) casts, var_export() and print_r() leave out caps, roles and allcaps until one of them is first accessed. On PHP 7.4 there is no way to hook into these.
  • On multisite, has_cap() for a super admin returns before reading allcaps, so it doesn't trigger the load.
  • get_authordata() builds the user directly, so it bypasses the pluggable get_userdata().

For all users:

  • WP_User::__get() now returns by reference. An in-place write to a meta key, such as $user->some_meta['k'] = 1, used to raise an "Indirect modification" notice and is now silently ignored. It never had any effect either way.

Note

This defers loading the capability data but not the full user meta, because cache_users() still primes all user meta. The full front-end saving depends on lazy user meta (#63021).

Test plan

  • New tests:
    • tests/phpunit/tests/user/wpUserLazyCapabilities.php (lazy and eager behaviour, magic methods, JSON, serialize, for_site() on single site and multisite, the super admin case)
    • tests/phpunit/tests/user/getAuthordata.php
  • Call-site tests, each checking that user meta isn't loaded, in getTheModifiedAuthor.php, getTheAuthorMeta.php, getTheAuthorPostsLink.php, wpListAuthors.php and link.php.
  • generatePostdata.php and setupPostdata.php updated for get_authordata().
  • capabilities.php: get_object_vars() includes allcaps for a WP_User that hasn't been touched yet.
  • The 58001 group and the user,capabilities,query,author,post,rest-api,template,link groups pass on single site and multisite (PHP 7.4).
  • Manual smoke test of author archives and the $authordata global.

🤖 Generated with Claude Code

Introduce `get_authordata()`, a lighter-weight alternative to `get_userdata()` for contexts that mainly need display data, such as the `$authordata` global set up in the Loop, author archives, and the author template tags.

The returned `WP_User` defers loading the `caps`, `roles`, and `allcaps` properties until they are first accessed. `WP_User::init()` gains a `$short_init` parameter that unsets these properties, so that the first access is routed through the magic methods, which load the data on demand. `WP_User::__get()` now returns by reference, so the properties can still be modified in place. `WP_User` implements `JsonSerializable` and `__serialize()`, so that `wp_json_encode()` and `serialize()` include the capability data.

By default, `WP_User` still loads the capability data immediately, so `get_object_vars()` and similar functions are unchanged for all other users.

Follow-up to [60915], [61037].

Props spacedmonkey.
See #58001.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026

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.

Core Committers: Use this line as a base for the props when committing in SVN:

Props spacedmonkey.

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

spacedmonkey and others added 2 commits October 10, 2026 00:11
…data().

The `get_userdata()` calls in `WP_Query::generate_postdata()` and `get_permalink()` were replaced with `get_authordata()`, which is passed an integer, so the ignored `argument.type` errors no longer occur.

See #58001.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The capability data is always loaded before serializing, so the pending flag is always false and can be serialized as is, instead of being removed from the data.

See #58001.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant