Skip to content

[2.x] fix: resolve VersionerInterface from container; cache rev-manifest reads#4638

Merged
imorland merged 1 commit into
2.xfrom
im/versioner-binding-and-cache
May 6, 2026
Merged

[2.x] fix: resolve VersionerInterface from container; cache rev-manifest reads#4638
imorland merged 1 commit into
2.xfrom
im/versioner-binding-and-cache

Conversation

@imorland

@imorland imorland commented May 6, 2026

Copy link
Copy Markdown
Member

Summary

RevisionCompiler, JsDirectoryCompiler, and Document declared a VersionerInterface property but instantiated FileVersioner directly in their constructors. The interface contract was unenforceable — $container->bind(VersionerInterface::class, MyVersioner::class) had no effect on these classes.

This PR:

  1. Binds VersionerInterface in FrontendServiceProvider (defaulting to FileVersioner against the flarum-assets disk) and resolves it via the container in all three call sites. Local-disk installs are unchanged (same default binding); installs on remote storage can now bind a faster implementation and have it take effect everywhere.
  2. Caches the parsed manifest in-memory on FileVersioner. With the binding now a singleton, all ~6 compilers + Document per request share one VersionerInterface instance — previously each read and JSON-decoded rev-manifest.json independently. On S3-backed installs this was ~12 round-trips per request just for asset URL resolution; now it's one.

Fixes #4637

Changes

  • framework/core/src/Frontend/FrontendServiceProvider.php — bind VersionerInterface to a FileVersioner singleton scoped to the flarum-assets filesystem.
  • framework/core/src/Frontend/Compiler/RevisionCompiler.php and JsDirectoryCompiler.php — drop the inline new FileVersioner(...), accept VersionerInterface via constructor (autowired). Both classes are @internal and resolved through the container by Frontend\Assets, so no caller needs updating.
  • framework/core/src/Frontend/Document.php — replace the FilesystemFactory $filesystem constructor parameter with VersionerInterface $versioner. Resolved via $container->makeWith() from Frontend.php — autowiring picks up the new arg.
  • framework/core/src/Frontend/Compiler/FileVersioner.php — per-instance manifest cache with write-through. putRevision() updates both disk and the cache; getRevision() and allRevisions() read from the cache. The CLI compile path constructs fresh containers, so per-instance lifetime is correct (no stale-manifest worries across processes).
  • New framework/core/tests/unit/Frontend/Compiler/FileVersionerTest.php — 5 tests pinning down read-once-per-instance, missing keys, missing manifest, write-through cache update, and key removal via null revision.

Backward compatibility

  • Document::__construct signature change is the only break. The sixth parameter FilesystemFactory $filesystem is now VersionerInterface $versioner. Subclasses that call parent::__construct(...) with positional arguments will need to update; named arguments are forward-compatible. Most extensions don't subclass Document and aren't affected. Worth a note in the 2.0 dev upgrade guide.
  • RevisionCompiler and JsDirectoryCompiler are @internal and only resolved through the container; the new constructor parameter is autowired from the binding.

Test plan

  • Unit tests: 5/5 pass (FileVersionerTest).
  • Frontend integration tests: 10/10 pass (full document rendering exercises all three resolved instantiation paths).
  • Admin integration tests: 2/2 pass.
  • Manual: confirm forum/admin pages render normally on a local-disk install.
  • Manual: confirm php flarum assets:publish and php flarum cache:clear still write a valid rev-manifest.json and bundles serve correctly.
  • Manual on S3 install (if available): confirm filesystem call count for asset URL resolution drops from ~12 to 1 per request.

Related

RevisionCompiler, JsDirectoryCompiler, and Document declared a
VersionerInterface property but instantiated FileVersioner directly in
their constructors. The interface contract was unenforceable — code
binding VersionerInterface to a custom implementation had no effect.

Bind VersionerInterface in FrontendServiceProvider (defaulting to
FileVersioner against the flarum-assets disk) and resolve it via the
container in all three call sites. Local-disk installs are unchanged
(same default binding); installs on remote storage can now bind a
faster implementation (DB, Redis, etc.) and have it take effect
everywhere.

Also cache the parsed manifest in-memory on FileVersioner. With the
binding now a singleton, all ~6 compilers plus Document per request
share one VersionerInterface instance — previously they each read and
JSON-decoded rev-manifest.json independently. On S3-backed installs
this was ~12 round-trips per request just for asset URL resolution.

Document's constructor signature changes: the sixth parameter
FilesystemFactory $filesystem is now VersionerInterface $versioner.
Subclasses calling parent::__construct() positionally need updating;
named args are forward-compatible.

Fixes #4637
@imorland imorland requested a review from a team as a code owner May 6, 2026 23:42
@imorland imorland added this to the 2.0.0-rc.2 milestone May 6, 2026
@imorland imorland merged commit 553a4c0 into 2.x May 6, 2026
25 checks passed
@imorland imorland deleted the im/versioner-binding-and-cache branch May 6, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.x] Frontend asset compilers hardcode FileVersioner, ignoring VersionerInterface container binding

1 participant