Skip to content

Commit

Permalink
Feat: Add possibility to refresh Cache, regardless of TTL (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-schmetter authored Jul 4, 2024
1 parent 363649e commit 1401ffe
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 55 deletions.
131 changes: 76 additions & 55 deletions src/Repository/DefaultUnleashRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,66 +116,21 @@ public function getFeatures(): iterable
{
$features = $this->getCachedFeatures();
if ($features === null) {
$data = null;
if (!$this->configuration->isFetchingEnabled()) {
if (!$rawData = $this->getBootstrappedResponse()) {
throw new LogicException('Fetching of Unleash api is disabled but no bootstrap is provided');
}
} else {
$request = $this->requestFactory
->createRequest('GET', (string) Url::appendPath($this->configuration->getUrl(), 'client/features'))
->withHeader('UNLEASH-APPNAME', $this->configuration->getAppName())
->withHeader('UNLEASH-INSTANCEID', $this->configuration->getInstanceId())
->withHeader('Unleash-Client-Spec', '4.3.2')
;

foreach ($this->configuration->getHeaders() as $name => $value) {
$request = $request->withHeader($name, $value);
}

try {
$response = $this->httpClient->sendRequest($request);
if ($response->getStatusCode() === 200) {
$rawData = (string) $response->getBody();
$data = json_decode($rawData, true);
if (($lastError = json_last_error()) !== JSON_ERROR_NONE) {
throw new InvalidValueException(
sprintf("JsonException: '%s'", json_last_error_msg()),
$lastError
);
}
$this->setLastValidState($rawData);
} else {
throw new HttpResponseException("Invalid status code: '{$response->getStatusCode()}'");
}
} catch (Exception $exception) {
$this->configuration->getEventDispatcher()->dispatch(
new FetchingDataFailedEvent($exception),
UnleashEvents::FETCHING_DATA_FAILED,
);
$rawData = $this->getLastValidState();
}
$rawData ??= $this->getBootstrappedResponse();
if ($rawData === null) {
throw new HttpResponseException(sprintf(
'Got invalid response code when getting features and no default bootstrap provided: %s',
isset($response) ? $response->getStatusCode() : 'unknown response status code'
), 0, $exception ?? null);
}
}

if ($data === null) {
$data = json_decode($rawData, true);
}

assert(is_array($data));
$features = $this->parseFeatures($data);
$this->setCache($features);
$features = $this->fetchFeatures();
}

return $features;
}

/**
* @throws InvalidArgumentException
* @throws ClientExceptionInterface
*/
public function refreshCache(): void
{
$this->fetchFeatures();
}

/**
* @throws InvalidArgumentException
*
Expand Down Expand Up @@ -538,4 +493,70 @@ private function parseVariants(array $variantsRaw): array

return $variants;
}

/**
* @throws ClientExceptionInterface
* @throws InvalidArgumentException
*
* @return array<Feature>
*/
private function fetchFeatures(): array
{
$data = null;
if (!$this->configuration->isFetchingEnabled()) {
if (!$rawData = $this->getBootstrappedResponse()) {
throw new LogicException('Fetching of Unleash api is disabled but no bootstrap is provided');
}
} else {
$request = $this->requestFactory
->createRequest('GET', (string) Url::appendPath($this->configuration->getUrl(), 'client/features'))
->withHeader('UNLEASH-APPNAME', $this->configuration->getAppName())
->withHeader('UNLEASH-INSTANCEID', $this->configuration->getInstanceId())
->withHeader('Unleash-Client-Spec', '4.3.2');

foreach ($this->configuration->getHeaders() as $name => $value) {
$request = $request->withHeader($name, $value);
}

try {
$response = $this->httpClient->sendRequest($request);
if ($response->getStatusCode() === 200) {
$rawData = (string) $response->getBody();
$data = json_decode($rawData, true);
if (($lastError = json_last_error()) !== JSON_ERROR_NONE) {
throw new InvalidValueException(
sprintf("JsonException: '%s'", json_last_error_msg()),
$lastError
);
}
$this->setLastValidState($rawData);
} else {
throw new HttpResponseException("Invalid status code: '{$response->getStatusCode()}'");
}
} catch (Exception $exception) {
$this->configuration->getEventDispatcher()->dispatch(
new FetchingDataFailedEvent($exception),
UnleashEvents::FETCHING_DATA_FAILED,
);
$rawData = $this->getLastValidState();
}
$rawData ??= $this->getBootstrappedResponse();
if ($rawData === null) {
throw new HttpResponseException(sprintf(
'Got invalid response code when getting features and no default bootstrap provided: %s',
isset($response) ? $response->getStatusCode() : 'unknown response status code'
), 0, $exception ?? null);
}
}

if ($data === null) {
$data = json_decode($rawData, true);
}

assert(is_array($data));
$features = $this->parseFeatures($data);
$this->setCache($features);

return $features;
}
}
3 changes: 3 additions & 0 deletions src/Repository/UnleashRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

use Unleash\Client\DTO\Feature;

/**
* @method void refreshCache()
*/
interface UnleashRepository
{
public function findFeature(string $featureName): ?Feature;
Expand Down
26 changes: 26 additions & 0 deletions tests/Repository/DefaultUnleashRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,32 @@ public function testCacheWithNoFeatures()
self::assertNull($repository->findFeature('test'));
}

public function testRefreshCache()
{
$cache = $this->getRealCache();
$repository = new DefaultUnleashRepository(
$this->httpClient,
new HttpFactory(),
(new UnleashConfiguration('', '', ''))
->setCache($cache)
->setTtl(5)
);

$this->pushResponse($this->response, 3);
$repository->getFeatures();

sleep(3);
$repository->refreshCache();
sleep(3);

self::assertCount(2, $this->requestHistory);

//Cache entry should have been invalidated by now (3+3 > TTL) if we didn't refresh it
$repository->getFeatures();
//Cache was hit if we have no new requests
self::assertCount(2, $this->requestHistory);
}

public function testCustomHeaders()
{
$this->pushResponse($this->response);
Expand Down

0 comments on commit 1401ffe

Please sign in to comment.