Fix report transient cache key instability caused by DateTime microsecond serialization#64430
Conversation
…rialization When get_cache_key() serialized DateTime objects via wp_json_encode, the resulting cache key included microsecond precision from WC_DateTime. This caused a different transient key on every request, defeating caching. Normalize DateTimeInterface objects to ISO 8601 strings (DATE_ATOM format) before hashing, which strips microseconds and produces stable cache keys. Fixes woocommerce#64177
Testing GuidelinesHi , Apart from reviewing the code changes, please make sure to review the testing instructions (Guide) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed. Reminder: PR reviewers are required to document testing performed. This includes:
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughDateTime parameters used in WooCommerce Reports DataStore cache-key generation are normalized to ISO 8601 strings (DATE_ATOM) before encoding, preventing transient cache key instability caused by microsecond differences. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugins/woocommerce/tests/php/src/Admin/API/Reports/DataStore/CacheKeyTest.php`:
- Around line 69-78: The test currently only checks that $params['before'] is
still a DateTime instance; instead, capture a clone of the original DateTime
(e.g., $original = clone $dt) before calling $this->invoke_get_cache_key($store,
$params) and assert that the original and the post-call DateTime are equal in
both timestamp and timezone (for example compare formatted string or both
getTimestamp() and getTimezone()->getName()) so
CustomersDataStore::get_cache_key (invoked via invoke_get_cache_key) is proven
not to mutate the DateTime value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: ac6e651d-5d38-432a-b049-2271d0122f38
📒 Files selected for processing (3)
plugins/woocommerce/changelog/fix-64177-transient-cache-key-datetimeplugins/woocommerce/src/Admin/API/Reports/DataStore.phpplugins/woocommerce/tests/php/src/Admin/API/Reports/DataStore/CacheKeyTest.php
…after get_cache_key Address CodeRabbit feedback: use clone and compare getTimestamp() and getTimezone()->getName() instead of only asserting instanceof.
prettyboymp
left a comment
There was a problem hiding this comment.
Heads up: test_user_creation failure traces to a latent cache-invalidation bug your fix has surfaced — not a regression in your change
Nice work tracking down the microsecond-instability bug. The fix itself is correct, but it's exposing a separate latent bug in WC_Cache_Helper::get_transient_version() that's causing WC_Admin_Tests_API_Reports_Customers::test_user_creation to fail.
What's happening
The test does three things in the same wall-clock second:
- REST call #1 (no orders) → caches
[] - Create order +
WC_Helper_Queue::run_all_pending('wc-admin-data')→OrdersSchedulercallsReportsCache::invalidate()(correctly) - REST call #2 → expects 1 customer
Pre-fix, this passed because every request had unique microseconds in the cache key, so call #2 always missed the cache. Post-fix, calls #1 and #2 produce identical cache keys (correct second-precision behavior). But Cache::invalidate() calls WC_Cache_Helper::get_transient_version( 'woocommerce_reports', true ), which stamps the version with (string) time(). Within the same second, the "new" version equals the old one — invalidation becomes a silent no-op, and call #2 reads the stale [] from cache.
Reproducer:
$v1 = WC_Cache_Helper::get_transient_version( 'woocommerce_reports' ); // "1777776460"
WC_Cache_Helper::get_transient_version( 'woocommerce_reports', true ); // refresh
$v2 = WC_Cache_Helper::get_transient_version( 'woocommerce_reports' ); // still "1777776460"
// $v1 === $v2 → invalidation no-opSuggested path
The cache-helper bug is its own concern and shouldn't gate this PR — it's tracked at #64557.
For this PR, fix the failing test by bypassing the cache in this test class (it's a query-semantics test, not a cache test):
Preferred — disable caching for the whole test class via the existing filter:
public function setUp(): void {
parent::setUp();
add_filter( 'woocommerce_analytics_report_should_use_cache', '__return_false' );
}
public function tearDown(): void {
remove_filter( 'woocommerce_analytics_report_should_use_cache', '__return_false' );
parent::tearDown();
}This is a one-place declaration that the test class is exercising query semantics, not the cache layer — the filter at DataStore.php:307 exists for exactly this kind of caller.
Alternative — pass force_cache_refresh on the post-mutation reads:
$request->set_query_params( array( 'per_page' => 10, 'force_cache_refresh' => true ) );Equivalent for the failing assertion, but more verbose if multiple tests in the file have the same shape.
Either preserves the test's intent (verifying that admins-with-orders and customers appear in the customers list) without depending on cache-invalidation timing — please avoid sleep(1) since it slows the suite and hides the real timing assumption.
Otherwise the PR fix itself looks good — small, focused, well-tested, and addresses the cache-key instability accurately. Once the test is updated, this should be ready to merge.
The test_user_creation test exercises query semantics, not cache behaviour. Stable cache keys (introduced by the DateTime normalization fix) surfaced a latent cache-invalidation timing bug tracked in woocommerce#64557. Bypass the cache via the existing woocommerce_analytics_report_should_use_cache filter so the tests are not affected by the separate caching issue. Per review feedback from @prettyboymp on woocommerce#64430.
prettyboymp
left a comment
There was a problem hiding this comment.
@shsajalchowdhury, changes look good. Thank you.
Submission Review Guidelines:
Changes proposed in this Pull Request:
When
get_cache_key()serializedDateTimeobjects viawp_json_encode(), the resulting cache key included microsecond precision fromWC_DateTime. This produced a different transient key on every page load, completely defeating the report cache.Root cause:
get_default_query_vars()returnsTimeInterval::default_before()which creates aWC_DateTimeat the current moment with microseconds. Sincewp_json_encodepreserves sub-second precision, each request generates a unique MD5 hash, sowc_report_customers_*transients are written but never hit.Fix: Normalize
DateTimeInterfaceobjects to ISO 8601 strings (DATE_ATOMformat) inget_cache_key()before hashing. This strips microseconds and produces stable, deterministic cache keys while preserving second-level precision.The normalization happens on a local copy of
$params(PHP passes arrays by value), so the original$query_argswith theirDateTimeobjects remain intact for downstream SQL query building.Closes #64177
How to test the changes in this Pull Request:
wc_report_customers_*transient keywc_report_customers_*transient with a different key (due to microsecond variation in thebeforeparameter)Testing that has already taken place:
tests/php/src/Admin/API/Reports/DataStore/CacheKeyTest.phpverifying:beforeandafterparams produce stable keysget_cache_key()bin/lint-branch.shMilestone
Changelog entry
Changelog Entry Comment
Comment
A changelog file has been manually added to
plugins/woocommerce/changelog/fix-64177-transient-cache-key-datetimeas the automated changelog job cannot run on PRs from forks.