Skip to content

Cache missing Halcyon templates as a cache hit - #249

Open
austinderrick wants to merge 2 commits into
wintercms:developfrom
austinderrick:fix/halcyon-cache-missing-templates
Open

austinderrick wants to merge 2 commits into
wintercms:developfrom
austinderrick:fix/halcyon-cache-missing-templates

Conversation

@austinderrick

@austinderrick austinderrick commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Halcyon\Builder::getCached() stores query results with remember() / rememberForever(). When a single template does not exist, the stored result is null, and the cache repository treats a stored null as a miss. Every cached lookup of a missing template therefore reads the cache store twice (has(), then get()), asks the datasource for the file again, and writes the cache entry again. This happens on every request.

The CMS hits this path on every component partial render. ComponentPartial::loadOverrideCached() checks the theme for an override under two names (the lowercase alias and the exact alias), and most components have no override. A page with many component partials does hundreds of these lookups.

To repair, I've adjusted getCacheCallback() to store an empty result ([]) instead of null when the template is not found.

An empty array already means "no records" everywhere getCached() is used. Builder::get() does $results ?: [],
and isCacheBusted() treats an empty result as "no mtime". No other code changes.

When is it null?

Only for a single-template lookup (find() / whereFileName(), which sets selectSingle) when the template does not exist. getFresh() then calls Processor::processSelectOne(), which returns null whenever the datasource's selectOne() does: FileDatasource when the file cannot be read, and DbDatasource when no row matches. Multi-record queries (get() without a file name) return [] and are already cached correctly.

Tests

tests/Halcyon/HalcyonCacheTest.php adds three tests:

  • A missing template is read from the datasource once, then served from the cache, also after the memory cache is
    flushed to simulate a new request. Without the fix, this test fails with 3 datasource reads instead of 1.
  • A cached miss is busted when the template file is created, and the new template is returned.
  • An existing template is still served from the cache.

Summary by CodeRabbit

  • Bug Fixes

    • Repeated requests for a missing template now avoid unnecessary repeated lookups.
    • If a template is created after a request found it missing, it becomes available without requiring a cache flush.
    • Existing templates continue to be returned correctly from the cache.
  • Tests

    • Added coverage for cached missing templates, newly created templates, and existing templates.

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 028ade5f-dc27-44e5-a68d-aca7ebc398c8

📥 Commits

Reviewing files that changed from the base of the PR and between e97198d and 9201c9a.

📒 Files selected for processing (2)
  • src/Halcyon/Builder.php
  • tests/Halcyon/HalcyonCacheTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The cache callback converts a null result from processInitCacheData to an empty array before caching. The method’s parameter and return annotations now include null. Tests cover repeated lookups of missing and existing templates, and a template created after a cached miss.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 9201c

The supplied review identifies no issue that needs correction before merging; normal checks still apply.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 9201c

Missing templates can now be served from cache instead of repeatedly read from storage. The reviewed lookup still validates template names, and the new tests cover recovery when a template is created. No introduced security issue was established, although cache isolation in shared deployments remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The directly changed behavior affects cached template lookups sharing a cache repository and key. The key includes the model directory and selection fields, but not an explicit identity field; effective cross-context exposure depends on configuration not established here.

Trust Boundaries and Controls

  • observed — A caller-supplied filename still passes path and extension validation before single-file selection. Optional cache tags remain available, but their use by production callers was not established.

Resilience and Maintainability Implications

  • observed — Single-file freshness checks continue on cached hits, including empty results. A first miss and a later file-creation recovery are tested; atomic behavior under concurrent misses was not established.

Hardening Proposals

  • proposed — If distinct tenants or template sources can share the same directory and cache repository, verify that their cache drivers, tags, or keys isolate cached misses before relying on this behavior in such a deployment.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: caching missing Halcyon templates as cache hits.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

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