Skip to content

Make CssImportFilter hashable so the asset cache can key it - #52

Merged
LukeTowers merged 2 commits into
masterfrom
fix/cssimportfilter-hashable
Aug 14, 2026
Merged

Make CssImportFilter hashable so the asset cache can key it#52
LukeTowers merged 2 commits into
masterfrom
fix/cssimportfilter-hashable

Conversation

@LukeTowers

Copy link
Copy Markdown
Member

The bug

Any asset filtered by a configured CssImportFilter fatals as soon as it passes through AssetCache:

Serialization of 'Closure' is not allowed

AssetCache::getCacheKey() builds its key from the asset's filters, hashing those that implement HashableInterface and falling back to serialize() for the rest (AssetCache.php#L151-L157). Since #51, CssImportFilter can hold an import validator — normally a closure — and serializing a closure throws.

This reached consumers: Winter CMS wires an import validator into its asset combiner, so every combined bundle containing a plain .css file 500s with that message in place of the stylesheet. .less/.scss/.js bundles are unaffected, since CssImportFilter is only registered for css.

The fix

Implement HashableInterface so the filter is hashed rather than serialized. The hash covers the inner import filter and the validator, preferring serialization of each since that captures their configuration.

A closure can't be serialized, so it is identified by its declaration site plus the variables bound into it (ReflectionFunction::getStaticVariables()). That second half matters: the bound variables are where a validator's configuration lives — the set of paths it authorises — so two validators sharing a declaration but confining imports differently hash differently and cannot be served each other's cached output. Returning a constant for every closure would have let a permissive-validator build populate the cache and a restrictive-validator build reuse that output, undermining the confinement the validator exists to provide.

Object identity is deliberately never used as a fallback: spl_object_hash() and friends aren't stable between requests and would hand the cache a fresh key every time.

Tests

Five cases added to CssImportFilterTest, each verified to fail before the change:

  • the filter is hashable, and hashes with a validator set
  • AssetCache dumps an asset filtered by a validator-configured filter — the regression itself, which errors at AssetCache.php:155 without the fix
  • the hash is stable between equivalent instances, and differs with/without a validator
  • validators bound to different roots hash differently, while an equivalent configuration still hashes alike
  • validators declared in different places hash differently

Full suite passes: 417 tests, 0 failures.

Note for maintainers

Cache keys for assets filtered by CssImportFilter change with this fix, so those assets rebuild once on upgrade.

The second commit backfills the changelog, which stopped at 3.1.1 — 3.1.2 through 3.2.1 all shipped without entries. Reconstructed from the commits between each tag and dated from each published release. Happy to drop that commit if you'd rather it landed separately. The 3.2.2 heading is dated on the assumption of an imminent release; adjust as needed.

🤖 Generated with Claude Code

LukeTowers and others added 2 commits August 14, 2026 13:39
AssetCache builds its cache key from an asset's filters, hashing those that
implement HashableInterface and falling back to serialize() for the rest.
Since v3.2.1 CssImportFilter can hold an import validator, which is normally
a closure, and serializing a closure throws — so any asset filtered by a
configured CssImportFilter fataled with "Serialization of 'Closure' is not
allowed" as soon as it passed through the asset cache.

Implement HashableInterface so the filter is never serialized. The hash
covers the inner import filter and the validator, preferring serialization
of each, which captures configuration. A closure cannot be serialized, so it
is identified by its declaration site plus the variables bound into it —
which is what carries a validator's configuration, so two validators
confining imports to different roots cannot be served each other's cached
output. Object identity is deliberately never used: it is not stable between
requests and would give the cache a fresh key every time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The changelog stopped at 3.1.1; 3.1.2, 3.1.3, 3.1.4, 3.1.5, 3.1.6, 3.2.0 and
3.2.1 shipped without entries. Reconstructed from the commits between each
tag, dated from the published release rather than the local tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@LukeTowers
LukeTowers requested a review from a team as a code owner August 14, 2026 19:49
@LukeTowers
LukeTowers merged commit d8076ab into master Aug 14, 2026
9 checks passed
@LukeTowers
LukeTowers deleted the fix/cssimportfilter-hashable branch August 14, 2026 19:53
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