diff --git a/README.md b/README.md index 7a8da35..228c1a8 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Fastly domain purges invalidate the entire configured service. Use one domain pe ### Cache routing -`Cache\Adapter\Proxy` routes the application domain to one adapter, configured network domains to another, and fans custom domains out to every custom adapter. +`Cache\Adapter\Proxy` routes domain and path purges: it sends the application domain to one adapter, configured network domains to another, and fans custom domains out to every custom adapter. ```php use Utopia\Cdn\Cache\Adapter\Proxy; @@ -94,6 +94,17 @@ $cache = new Cache(new Proxy( )); ``` +Cache keys and tags are scoped to a Fastly service or Cloudflare zone. Consequently, `Proxy` does not route `purgeKeys()` calls. The consuming application must select the adapter using its own routing context before constructing `Cache`: + +```php +$adapter = $cdnAdapterResolver->resolve($rule); + +$cache = new Cache($adapter); +$cache->purgeKeys([ + 'domain-' . \strtolower($domain), +]); +``` + ## Certificates The current certificate provider support is focused on CDN-managed certificates through Fastly TLS subscriptions. diff --git a/src/Cdn/Cache/Adapter/Proxy.php b/src/Cdn/Cache/Adapter/Proxy.php index a889faf..27def0f 100644 --- a/src/Cdn/Cache/Adapter/Proxy.php +++ b/src/Cdn/Cache/Adapter/Proxy.php @@ -53,19 +53,9 @@ public function purgeKeys(array $keys): void return; } - $purged = false; - foreach ($this->all() as $adapter) { - try { - $adapter->purgeKeys($keys); - $purged = true; - } catch (UnsupportedOperation) { - continue; - } - } - - if (!$purged) { - throw new UnsupportedOperation('Cache key purging is not supported by any configured adapter.'); - } + throw new UnsupportedOperation( + 'Cache key purging cannot be routed by domain. Select the service or zone adapter before purging keys.' + ); } /** @return array */ @@ -85,17 +75,4 @@ private function select(string $domain): array return $this->customDomainAdapters; } - - /** @return array */ - private function all(): array - { - $adapters = [$this->appDomainAdapter, $this->networkAdapter, ...$this->customDomainAdapters]; - $unique = []; - - foreach ($adapters as $adapter) { - $unique[\spl_object_id($adapter)] = $adapter; - } - - return \array_values($unique); - } } diff --git a/tests/Cdn/Cache/Adapter/ProxyTest.php b/tests/Cdn/Cache/Adapter/ProxyTest.php index a48e302..dde0e07 100644 --- a/tests/Cdn/Cache/Adapter/ProxyTest.php +++ b/tests/Cdn/Cache/Adapter/ProxyTest.php @@ -10,7 +10,7 @@ class ProxyTest extends TestCase { - public function testRoutesAndFansOut(): void + public function testRoutesDomainsAndPaths(): void { $calls = new \ArrayObject(); $app = $this->adapter('app', $calls); @@ -22,9 +22,8 @@ public function testRoutesAndFansOut(): void $proxy->purgeDomain('app.example.com'); $proxy->purgePaths('network.example.com', ['/a']); $proxy->purgeDomain('customer.example.com'); - $proxy->purgeKeys(['key']); - $this->assertSame(['app:domain', 'network:paths', 'custom-a:domain', 'custom-b:domain', 'app:keys', 'network:keys', 'custom-a:keys', 'custom-b:keys'], $calls->getArrayCopy()); + $this->assertSame(['app:domain', 'network:paths', 'custom-a:domain', 'custom-b:domain'], $calls->getArrayCopy()); } public function testRejectsMissingCustomAdapters(): void @@ -35,33 +34,36 @@ public function testRejectsMissingCustomAdapters(): void $proxy->purgeDomain('custom.example.com'); } - public function testKeyPurgeSkipsUnsupportedAdapters(): void + public function testRejectsKeyPurgeWithoutServiceOrZoneSelection(): void { $calls = new \ArrayObject(); - $unsupported = $this->adapter('cloudflare', $calls, false); - $fastly = $this->adapter('fastly', $calls); - $proxy = new Proxy('app.example.com', $unsupported, $fastly, [$unsupported, $fastly]); + $app = $this->adapter('app', $calls); + $network = $this->adapter('network', $calls); + $custom = $this->adapter('custom', $calls); + $proxy = new Proxy('app.example.com', $app, $network, [$custom]); + $this->expectException(UnsupportedOperation::class); + $this->expectExceptionMessage('Select the service or zone adapter'); $proxy->purgeKeys(['key']); - - $this->assertSame(['fastly:keys'], $calls->getArrayCopy()); } - public function testKeyPurgeFailsWhenEveryAdapterIsUnsupported(): void + public function testEmptyKeyPurgeIsANoOp(): void { - $unsupported = $this->adapter('cloudflare', new \ArrayObject(), false); - $proxy = new Proxy('app.example.com', $unsupported, $unsupported, [$unsupported]); + $calls = new \ArrayObject(); + $adapter = $this->adapter('adapter', $calls); + $proxy = new Proxy('app.example.com', $adapter, $adapter, [$adapter]); - $this->expectException(UnsupportedOperation::class); - $proxy->purgeKeys(['key']); + $proxy->purgeKeys([]); + + $this->assertSame([], $calls->getArrayCopy()); } /** @param \ArrayObject $calls */ - private function adapter(string $name, \ArrayObject $calls, bool $supportsKeys = true): Adapter + private function adapter(string $name, \ArrayObject $calls): Adapter { - return new class ($name, $calls, $supportsKeys) implements Adapter { + return new class ($name, $calls) implements Adapter { /** @param \ArrayObject $calls */ - public function __construct(private string $name, private \ArrayObject $calls, private bool $supportsKeys) + public function __construct(private string $name, private \ArrayObject $calls) { } public function purgePaths(string $domain, array $paths): void @@ -74,9 +76,6 @@ public function purgeDomain(string $domain): void } public function purgeKeys(array $keys): void { - if (!$this->supportsKeys) { - throw new UnsupportedOperation('Unsupported.'); - } $this->calls->append($this->name . ':keys'); } };