Skip to content

Comments: Add wp_update_comment_counts() to reconcile stored counts#58

Open
adamsilverstein wants to merge 3 commits into
feature/65537-excluded-comment-types-filterfrom
feature/65537-update-comment-counts
Open

Comments: Add wp_update_comment_counts() to reconcile stored counts#58
adamsilverstein wants to merge 3 commits into
feature/65537-excluded-comment-types-filterfrom
feature/65537-update-comment-counts

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Follow-up to the #12310 counter fix, addressing the second half of pfefferle's review on Trac #35214 comment:52: keeping a post's stored comment_count consistent when the set of excluded comment types changes.

The gap

wp_update_comment_count_now() (made filter-aware in WordPress#12310) only refreshes a post's stored comment_count when that post's comments change. So a count written before a type joined the excluded set stays stale until the post sees activity again. Example: a site runs with review comments counting normally, then installs a plugin that excludes review via default_excluded_comment_types - existing posts keep their inflated count.

Why not auto-recount on registration

register_comment_type() runs on every request during init; it is not a persisted state transition, and the exclusion set is driven by the default_excluded_comment_types filter, not by register/unregister. There is no clean event to hook. Investigated alternatives and rejected them:

  • Recount on unregister_comment_type - wrong event (exclusion changes come from the filter, not unregister) and per-request.
  • "Dirty set" transient + lazy recount on read - would UPDATE during a GET (breaks read replicas / page caches; get_comments_number() is deliberately read-only) and relies on diffing a per-request filter output on the hot path.

The closest core analog confirms the right pattern: taxonomy term counts are recalculated only on data events, never on taxonomy registration. Rewrite rules use the same model - core exposes flush_rewrite_rules() and documents "call it on activation" rather than auto-detecting.

This PR

wp_update_comment_counts( $post_ids = null ) - a bulk recount helper that recomputes one or more posts' counts through the now filter-aware wp_update_comment_count_now(), so it honors the same exclusion set by construction (no new SQL). A plugin that changes the excluded set calls it once, typically from its activation hook; it also gives a future WP-CLI/admin maintenance tool a single correct entry point.

  • null (default) recalculates every post that has at least one comment.
  • An int or array of post IDs limits the work (deduped, invalid IDs dropped).
  • Returns the number of posts recalculated.

Naming follows the core wp_update_*_counts() bulk family (wp_update_user_counts(), wp_update_network_counts()); singular wp_update_comment_count() already exists for a single post.

Tests

New tests/phpunit/tests/comment/wpUpdateCommentCounts.php: empty input returns 0; targeted IDs only touch those posts; duplicate IDs are deduped; null recalculates all posts with comments; and the headline case - a newly-excluded review type drops a previously stored count to 0.

  • 562 --group comment tests pass (557 + 5 new).
  • PHPCS clean (0 errors), PHPStan clean.

Review updates

  • wp_update_comment_counts() now bumps the comment last_changed cache key. Comment query caches are salted only by that key, so the activation-time register-filter-then-recount flow is now fully self-consistent on persistent object caches.
  • The null path additionally visits posts with a nonzero stored count but no remaining comment rows - a SELECT DISTINCT on the comments table cannot see those - and iterates in keyset batches of 1000 instead of materializing every post ID.
  • Negative IDs are skipped instead of being absint()-coerced into valid ones.
  • Docs now name the per-post side effects: the edit_post hooks fire per recount, so cache purgers and indexers run once per post.
  • New tests cover the null-path return value, invalid IDs, stale-count resets on commentless posts via both paths, and the last_changed bump.

Stacked on WordPress#12310 (feature/65537-excluded-comment-types-filter); retarget to trunk once that lands.

wp_update_comment_count_now() only refreshes a post's stored comment_count
when that post's comments change, so an existing count can become stale
after the set of excluded comment types changes (for example when a plugin
registers a type that opts out of default listings via
default_excluded_comment_types). Registration runs on every request and is
not a state transition, so there is no safe automatic trigger; the
established core pattern for this is an explicit recount, like
flush_rewrite_rules() for rewrite rules.

Add a bulk recount helper that recomputes one or more posts' counts through
wp_update_comment_count_now(), so it honors the same exclusion filter. A
plugin that changes the excluded set calls it once, typically on activation.

See #35214, #65537.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7cbf8a18-5ec9-492d-9d2d-001a2368ec88

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/65537-update-comment-counts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- Bump the comment 'last_changed' cache key: comment query results are
  salted only by that key, so on a persistent object cache the
  activation-time flow (register the exclusion filter, then recount)
  would otherwise keep serving results cached under the previous
  excluded set.
- Include posts with a nonzero stored count but no remaining comment
  rows in the null path: SELECT DISTINCT on the comments table cannot
  see a post whose rows were all deleted while its stored count is
  stale.
- Iterate the null path in keyset batches of 1000 instead of
  materializing every post ID in memory.
- Skip negative IDs instead of silently recounting their absolute
  value, and document the per-post side effects (edit_post hooks fire
  for cache purgers and indexers) alongside the cache-invalidation
  behavior.
- Tests: null-path return value, invalid/negative/nonexistent IDs,
  stale-count reset for commentless posts on both paths, and the
  last_changed bump.
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