perf: Memoize is_user_admin, is_preview_request and viewing_as_spec per request#1553
perf: Memoize is_user_admin, is_preview_request and viewing_as_spec per request#1553jhalitschke wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to improve runtime performance on high-post-count archive pages by memoizing a few frequently-invoked “request-constant” methods in the Newspack Popups plugin.
Changes:
- Added local-static memoization to
Newspack_Popups::is_preview_request()andNewspack_Popups::is_user_admin(). - Added local-static memoization to
Newspack_Popups_View_As::viewing_as_spec(). - Updated (but introduced issues in) the
is_preview_request()docblock sentence.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| includes/class-newspack-popups.php | Memoizes preview/admin checks to reduce repeated capability/query evaluation work. |
| includes/class-newspack-popups-view-as.php | Memoizes “view as” spec resolution to avoid repeated admin + query param parsing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| /** | ||
| * Is it a preview request – a single popup preview or using "view as" feature. | ||
| * Is it a preview request ? a single popup preview or using "view as" feature. |
There was a problem hiding this comment.
The docblock punctuation/grammar was changed to "Is it a preview request ? ..." which reads incorrectly (space before "?" and uses "?" where the sentence continues). Please update it to a grammatically correct form (e.g., "Is it a preview request? ..." or "Is it a preview request — ...").
| * Is it a preview request ? a single popup preview or using "view as" feature. | |
| * Is it a preview request? A single popup preview or using the "view as" feature. |
| public static function is_preview_request() { | ||
| static $result = null; | ||
| if ( null !== $result ) { | ||
| return $result; | ||
| } | ||
| $is_customizer_preview = is_customize_preview(); | ||
| // Used by the Newspack Plugin's Campaigns Wizard. | ||
| $is_view_as_preview = false != Newspack_Popups_View_As::viewing_as_spec(); | ||
| return ! empty( self::previewed_popup_id() ) || ! empty( self::preset_popup_id() ) || $is_view_as_preview || $is_customizer_preview; | ||
| $result = ! empty( self::previewed_popup_id() ) || ! empty( self::preset_popup_id() ) || $is_view_as_preview || $is_customizer_preview; | ||
| return $result; |
There was a problem hiding this comment.
Memoizing the preview-request result in a local static can leak across multiple simulated requests in the PHPUnit suite (tests call go_to() repeatedly with different query strings). Once cached, later renders in the same PHP process may get a stale preview/non-preview value. Consider caching per request context (e.g., keyed by relevant $_GET values / request URI) or adding an explicit reset hook used by tests before each go_to()/render.
| @@ -971,7 +980,8 @@ public static function is_user_admin() { | |||
| * @return string Filtered capability string. | |||
| */ | |||
| $capability = apply_filters( 'newspack_popups_admin_user_capability', 'edit_others_pages' ); | |||
| return is_user_logged_in() && current_user_can( $capability ); | |||
| $result = is_user_logged_in() && current_user_can( $capability ); | |||
| return $result; | |||
There was a problem hiding this comment.
is_user_admin() is now memoized with a single static $result, which becomes stale if the current user changes within the same PHP process (the test suite calls wp_set_current_user() mid-test). This will cause admin checks to keep returning the first computed value. Cache should be keyed by get_current_user_id() (or invalidated on the set_current_user action) so it remains correct when user context changes.
| * Get the "view as" feature specification. | ||
| * | ||
| * @return string "View as" specification. | ||
| */ | ||
| public static function viewing_as_spec() { | ||
| static $result = null; | ||
| if ( null !== $result ) { |
There was a problem hiding this comment.
The @return docblock for viewing_as_spec() says it returns a string, but the function can return false. Please update the docblock to reflect the actual return type (e.g., string|false) so callers and static analysis don’t get incorrect type information.
| public static function viewing_as_spec() { | ||
| static $result = null; | ||
| if ( null !== $result ) { | ||
| return $result; | ||
| } | ||
| if ( ! Newspack_Popups::is_user_admin() ) { | ||
| return false; | ||
| $result = false; | ||
| return $result; | ||
| } | ||
| if ( isset( $_GET['view_as'] ) && $_GET['view_as'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | ||
| return sanitize_text_field( $_GET['view_as'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| $result = sanitize_text_field( $_GET['view_as'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| return $result; | ||
| } | ||
| $result = false; | ||
| return $result; |
There was a problem hiding this comment.
viewing_as_spec() now memoizes into a single static $result. In PHPUnit (and in any long-running PHP process), user context and $_GET['view_as'] can change between calls; once cached, this method can keep returning a stale spec/false even after wp_set_current_user() or go_to() updates the request. Prefer caching keyed by current user ID and the raw view_as value, or provide a reset mechanism for tests.
|
@jhalitschke can you please address the feedback from the Copilot code review? If you disagree with any feedback that's no problem, just respond to the thread with an explanation before you resolve it. We'll do a full code review after all Copilot feedback is resolved. |
All Submissions:
Changes proposed in this Pull Request:
Summary
Newspack_Popups::is_user_admin(),Newspack_Popups::is_preview_request()andNewspack_Popups_View_As::viewing_as_spec()using static variables so each method computes its result onlyonce per request.
Problem
On archive pages with many posts, these methods are called repeatedly per popup x post combination:
is_user_admin()is_preview_request()viewing_as_spec()The call chain is:
assess_taxonomy_filter (96x) -> is_preview_request -> viewing_as_spec -> is_user_admin
show_admin_bar (22x) -> is_preview_request -> viewing_as_spec -> is_user_admin
parse_view_as (12x) -> viewing_as_spec -> is_user_admin
is_user_admin()callsapply_filters()+current_user_can()every time.is_preview_request()callsis_customize_preview(),previewed_popup_id(),preset_popup_id()andviewing_as_spec()every time. None of these results can change within a single HTTP request.On a test archive page with 48 posts and ~91 evaluated popups, the total overhead is ~5ms. The cost scales linearly with post count x popup count.
Fix
Add
static $resultmemoization to all three methods. The first call computes the result; subsequent calls return the cached value immediately.This pattern is safe because:
$_GET['view_as']is constant within a requestpreviewed_popup_id()/preset_popup_id()read from$_GETwhich is constantis_customize_preview()is constant within a requestTest plan
?view_as=...parameter)Other information: