Skip to content

Add balancer cache adapter with typed CDN options - #5

Merged
Meldiron merged 1 commit into
mainfrom
feat-balancer-adapter
Aug 13, 2026
Merged

Add balancer cache adapter with typed CDN options#5
Meldiron merged 1 commit into
mainfrom
feat-balancer-adapter

Conversation

@Meldiron

@Meldiron Meldiron commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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 Cache per 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 a Utopia\Balancer\Balancer and 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 into Exception\Purge (with getErrors() per provider), so one provider outage cannot silently skip the others. No option matching the filters raises Exception\Configuration rather than passing quietly.
  • Extend\CdnOption — a balancer Option with 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, since setState() stays public. It also carries the provider names as PROVIDER_FASTLY / PROVIDER_CLOUDFLARE constants, 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 $filters is private. utopia-php/balancer#10 added getFilteredOptions(), released as 0.4.1composer.json requires ^0.4.1, no dev branches. That release also moved balancer's own utopia-php/telemetry pin to ^0.4.0, without which this library could not install balancer at all: utopia-php/client pulls utopia-php/pools, which requires ^0.4.6.

Rebased on the merged #6 and #7

  • purgeZone() is now part of Cache\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.
  • Nothing here references the Cache\Adapter\Proxy removed by Remove the Proxy cache adapter #7.

Test Plan

composer test — 50 tests, 112 assertions, from 22/45 on main. New coverage in tests/Cdn/Cache/Adapter/BalancerTest.php and tests/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:

Regression introduced Result
error collection replaced by a rethrow (short-circuit at first failure) Errors: 1 — the assertion that Cloudflare is still purged after Fastly failed
UnsupportedOperation skip removed Errors: 1, Failures: 1
purgeZone() reading getOptions() instead of getFilteredOptions() Failures: 4, printing +'fastly-edge:zone' — the filtered-out edge service purged
purgeZone() removed from the adapter PHP refuses to load the class: must therefore be declared abstract or implement the remaining method — the interface enforcing the feature set, as intended

The README example was executed from the markdown against this branch: it resolves every import, leaves 2 survivors: fastly, cloudflare with the edge option correctly excluded, and reports balancer 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"}) and composer analyse (level 6, [OK] No errors) both clean.

Related PRs and Issues

Checklist

  • I read the contributing guide
  • I ran composer lint
  • I ran composer analyse
  • I ran composer test

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown

Greptile Summary

The 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.

  • Adds the Balancer cache adapter and Purge exception.
  • Adds CdnOption for typed adapter, provider, and edge metadata.
  • Adds balancer coverage and usage documentation.
  • Adds utopia-php/balancer 0.4.1 as a runtime dependency.

Confidence Score: 4/5

The 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

Filename Overview
src/Cdn/Cache/Adapter/Balancer.php Adds filtered purge fan-out and error aggregation, but the previously reported mutable-provider failure path remains outstanding.
src/Cdn/Extend/CdnOption.php Adds typed accessors over mutable balancer option state, including runtime validation of adapter and provider values.
src/Cdn/Exception/Purge.php Adds an aggregate runtime exception exposing the underlying provider failures.
composer.json Adds utopia-php/balancer 0.4.1 as a production dependency.
tests/Cdn/Cache/Adapter/BalancerTest.php Covers fan-out, filtering, unsupported operations, aggregation, validation, and empty purges without resolving the outstanding mutable-provider failure path.
README.md Documents balancer configuration, filtering, fan-out semantics, and failure aggregation.

Reviews (6): Last reviewed commit: "Add balancer cache adapter with typed CD..." | Re-trigger Greptile

Comment thread src/Cdn/Cache/Adapter/Balancer.php Outdated
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>
@Meldiron
Meldiron force-pushed the feat-balancer-adapter branch from f66e10a to 3493cc0 Compare August 13, 2026 19:40
@Meldiron
Meldiron merged commit f933936 into main Aug 13, 2026
4 checks passed
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.

2 participants