diff --git a/README.md b/README.md index 71af2dd..507ca74 100644 --- a/README.md +++ b/README.md @@ -188,9 +188,7 @@ Durability only applies when connectors throw a recoverable exception type. If a Caching ------- -Caching is available at the connector level if the connector implements `CacheToggle`. Connectors typically extend `CachingConnector` which implements [PSR-6][PSR-6]-compatible caching. Porter ships with just one cache implementation, `MemoryCache`, which stores data in memory but this can be substituted for any PSR-6 cache if the connector permits it. - -When available, the connector caches raw responses for each unique [cache key](#cache-key). Cache keys are generated by an implementation-defined strategy or the default `JsonCacheKeyGenerator` strategy. +Caching is available at the connector level if the connector implements `CacheToggle`. Connectors typically extend `CachingConnector` which implements [PSR-6][PSR-6]-compatible caching. Porter ships with just one cache implementation, `MemoryCache`, which stores data in memory but this can be substituted for any PSR-6 cache if the connector permits it. When available, the connector caches raw responses for each unique [cache key](#cache-keys). ### Cache advice @@ -214,15 +212,23 @@ $records = $porter->import( ); ``` -### Cache key +### Cache keys -The cache key can optionally be generated by an implementation of `CacheKeyGeneratorInterface` if the connector permits it. This implementation should provide one method `generateCacheKey` which returns a [PSR-6][PSR-6]-compatible cache key. +The cache key is generated by a `CacheKeyGenerator` that encodes parameters to produce a unique cache key for each distinct `Connector::fetch` request. The default `JsonCacheKeyGenerator` simply JSON-encodes the parameters to create a cache key. The cache key generation strategy may be changed by calling `CachingConnector::setCacheKeyGenerator`. -The default implementation `JsonCacheKeyGenerator` generates keys comprised of the source and options parameters passed to `Connector::fetch`. Options are sorted before the cache key is created so the order of options are insignificant. +#### Writing a cache key generator -#### Implementation example +The `CacheKeyGenerator` interface defines one method with the following interface. + +```php +public function generateCacheKey(string $source, array $sortedOptions) : string; +``` + +Implementations receive arguments similar to [connectors](#connectors), with the notable exception that the options parameter is converted to an array and sorted so that the same options specified in a different order result in the same cache key. The method must return a [PSR-6][PSR-6] compatible cache key. + +##### Implementation example -The following example demonstrates a simple cache key generation implementation using an md5 hash of the json encoded parameters. +The following example demonstrates cache key generation using a hash of JSON-encoded parameters. ```php class MyCacheKeyGenerator implements CacheKeyGenerator diff --git a/src/Connector/CachingConnector.php b/src/Connector/CachingConnector.php index 582629d..834dadc 100644 --- a/src/Connector/CachingConnector.php +++ b/src/Connector/CachingConnector.php @@ -108,13 +108,14 @@ public function setCacheKeyGenerator(CacheKeyGenerator $cacheKeyGenerator) * * @return string * - * @throws InvalidCacheKeyException + * @throws InvalidCacheKeyException Cache key contains invalid data. */ private function validateCacheKey($key) { if (!is_string($key)) { - throw new InvalidCacheKeyException('Cache key must be of type string.'); + throw new InvalidCacheKeyException('Cache key must be a string.'); } + if (strpbrk($key, self::RESERVED_CHARACTERS) !== false) { throw new InvalidCacheKeyException( sprintf('Cache key "%s" contains one or more reserved characters: "%s"', $key, self::RESERVED_CHARACTERS) diff --git a/test/Integration/Porter/Connector/CachingConnectorTest.php b/test/Integration/Porter/Connector/CachingConnectorTest.php index 4345561..a31370b 100644 --- a/test/Integration/Porter/Connector/CachingConnectorTest.php +++ b/test/Integration/Porter/Connector/CachingConnectorTest.php @@ -97,8 +97,6 @@ public function testCacheUsedForCacheKeyGenerator() public function testFetchThrowsInvalidCacheKeyExceptionOnNonStringCackeKey() { - $this->setExpectedException(InvalidCacheKeyException::class, 'Cache key must be of type string.'); - $this->connector->setCacheKeyGenerator( \Mockery::mock(CacheKeyGenerator::class) ->shouldReceive('generateCacheKey') @@ -107,6 +105,7 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonStringCackeKey() ->getMock() ); + $this->setExpectedException(InvalidCacheKeyException::class, 'Cache key must be a string.'); $this->connector->fetch('quux', $this->options); } @@ -114,14 +113,6 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonPSR6CompliantCacheKe { $cacheKey = CachingConnector::RESERVED_CHARACTERS; - $this->setExpectedException( - InvalidCacheKeyException::class, - sprintf('Cache key "%s" contains one or more reserved characters: "%s"', - $cacheKey, - CachingConnector::RESERVED_CHARACTERS - ) - ); - $this->connector->setCacheKeyGenerator( \Mockery::mock(CacheKeyGenerator::class) ->shouldReceive('generateCacheKey') @@ -130,6 +121,7 @@ public function testFetchThrowsInvalidCacheKeyExceptionOnNonPSR6CompliantCacheKe ->getMock() ); + $this->setExpectedException(InvalidCacheKeyException::class, 'contains one or more reserved characters'); $this->connector->fetch('quux', $this->options); }