Add balancer cache adapter with typed CDN options - #5
Conversation
Greptile SummaryThe PR introduces a composite cache adapter that applies balancer filters and fans purge operations out across matching CDN options, together with typed option accessors and aggregated purge errors.
Confidence Score: 4/5The PR is not yet safe to merge because malformed mutable provider state can still abort purge fan-out and leave later CDN providers unpurged. The adapter retrieves mutable provider metadata while handling an adapter failure; if that metadata has an invalid type, getProvider() throws from inside the catch block, replacing the aggregate failure and terminating iteration before remaining providers are purged. Files Needing Attention: src/Cdn/Cache/Adapter/Balancer.php and src/Cdn/Extend/CdnOption.php Important Files Changed
Reviews (6): Last reviewed commit: "Add balancer cache adapter with typed CD..." | Re-trigger Greptile |
Provider selection lived in every consumer: read the env, decide which of
Fastly and Cloudflare applies, build one Cache per provider, call a
different purge method on each. That logic is the same everywhere and is
wrong in the same way everywhere, so it moves here.
Cache\Adapter\Balancer takes a utopia-php/balancer Balancer and purges
through every option its filters leave standing, attempting each
independently and aggregating failures into Exception\Purge, so one
provider outage cannot silently skip the rest. No option matching the
filters raises Exception\Configuration rather than passing quietly.
Extend\CdnOption wraps a balancer Option with typed accessors, so a filter
reads getProvider()/isEdge() instead of getState('provider'), and carries
the provider names as constants rather than a separate enum class.
purgeZone() is implemented too, since #6 put it on the interface: it fans
out like the others, which makes it the widest purge available here, and
only the filters keep it away from the options they exclude. AdapterTest
now covers this adapter as well, so a composite that lagged the interface
would fail there rather than silently stop forwarding an operation.
Requires utopia-php/balancer 0.4.1 for getFilteredOptions(): run() returns
one option and $filters is private, while a purge has to reach every
provider that may hold a response for the domain.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f66e10a to
3493cc0
Compare
What does this PR do?
Moves CDN provider selection out of consumers and into the library.
Today every consumer reads its own env vars, decides which of Fastly and Cloudflare applies, builds one
Cacheper provider, and calls a different purge method on each because the providers disagree about what a domain purge is. That logic is identical in every consumer and wrong in the same way in every consumer.Cache\Adapter\Balancer— takes aUtopia\Balancer\Balancerand purges through every option its filters leave standing. The balancer holds the providers and the filters that narrow them, so provider selection becomes configuration: a caller states what to purge and which options qualify, never which API to call. Options are attempted independently and failures aggregated intoException\Purge(withgetErrors()per provider), so one provider outage cannot silently skip the others. No option matching the filters raisesException\Configurationrather than passing quietly.Extend\CdnOption— a balancerOptionwith typed accessors. A filter reads$option->getProvider()/$option->isEdge()instead of$option->getState('provider'), so key spelling and value types stop being the caller's problem. Getters re-check types on the way out, sincesetState()stays public. It also carries the provider names asPROVIDER_FASTLY/PROVIDER_CLOUDFLAREconstants, rather than a separate class for two strings.Exception\Purge— carries one throwable per failed provider.Requires utopia-php/balancer 0.4.1
The adapter needs every option that passed the filters, and
run()returns one while$filtersis private. utopia-php/balancer#10 addedgetFilteredOptions(), released as 0.4.1 —composer.jsonrequires^0.4.1, no dev branches. That release also moved balancer's ownutopia-php/telemetrypin to^0.4.0, without which this library could not install balancer at all:utopia-php/clientpullsutopia-php/pools, which requires^0.4.6.Rebased on the merged #6 and #7
purgeZone()is now part ofCache\Adapter, so this adapter implements it: it fans out like the other three, which makes it the widest purge reachable here — every matching provider drops its entire cache, for every domain it holds, not only the ones these options front. Only the filters keep it away from the options they exclude, and the README says so.tests/Cdn/Cache/AdapterTest.php(added by Fix Fastly domain purge, and make both cache adapters consistent #6) now covers this adapter too, so a composite that lagged the interface would fail there rather than silently stop forwarding an operation.Cache\Adapter\Proxyremoved by Remove the Proxy cache adapter #7.Test Plan
composer test— 50 tests, 112 assertions, from 22/45 onmain. New coverage intests/Cdn/Cache/Adapter/BalancerTest.phpandtests/Cdn/Extend/CdnOptionTest.php: fan-out across all survivors, filters narrowing to one, both providers reached for a custom domain, zone purge respecting the filters, aggregation after a mid-fan-out failure, unsupported options skipped, no-match / all-unsupported / untyped-option failures, empty purges touching nothing, domain validation, and typed accessors rejecting state overwritten with the wrong type.Behaviours seen red before being claimed:
Errors: 1— the assertion that Cloudflare is still purged after Fastly failedUnsupportedOperationskip removedErrors: 1, Failures: 1purgeZone()readinggetOptions()instead ofgetFilteredOptions()Failures: 4, printing+'fastly-edge:zone'— the filtered-out edge service purgedpurgeZone()removed from the adaptermust therefore be declared abstract or implement the remaining method— the interface enforcing the feature set, as intendedThe README example was executed from the markdown against this branch: it resolves every import, leaves
2 survivors: fastly, cloudflarewith the edge option correctly excluded, and reportsbalancer version in use: 0.4.1. It references pre-built adapters rather than constructing them, so it does not restate Fastly's constructor signature.composer lint({"result":"pass"}) andcomposer analyse(level 6,[OK] No errors) both clean.Related PRs and Issues
Checklist
composer lintcomposer analysecomposer test🤖 Generated with Claude Code