Skip to content

Instantly share code, notes, and snippets.

@davidlukac
Last active December 2, 2016 18:57
Show Gist options
  • Save davidlukac/1f7fa09ac1649b9b2b892e3df3e2176f to your computer and use it in GitHub Desktop.
Save davidlukac/1f7fa09ac1649b9b2b892e3df3e2176f to your computer and use it in GitHub Desktop.
FallBackCachItem and Spec
<?php
namespace NMH\Helper;
use Stash\Invalidation;
use Tedivm\StashBundle\Service\CacheService;
/**
* Class FallbackCacheItemRepository
*
* @package NMH\Helper
*/
class FallbackCacheItemRepository
{
/**
* Helper to retrieve given items from cache, underlying service, or from the fallback.
*
* @param string $cacheKey Cache key for the item to retrieve.
* @param string $fallbackKey Fallback cache key, in case underlying service is unavailable.
* @param CacheService $cacheService Cache service to get data from.
* @param callable $valueCallback Actual data service to get data from.
* @param int $cacheTtl Cache TTL - will be set if greater than 0 (optional).
* @param int $invalidationMethod Cache invalidation strategy.
*
* @return mixed Item retrieved from the cache.
*/
public function getValueFromCacheWithFallback(
string $cacheKey,
string $fallbackKey,
CacheService $cacheService,
callable $valueCallback,
int $cacheTtl = -1,
int $invalidationMethod = Invalidation::OLD
) {
$cacheItem = $cacheService->getItem($cacheKey);
$fallback = $cacheService->getItem($fallbackKey);
$cacheItem->setInvalidationMethod($invalidationMethod);
if ($cacheItem->isHit()) {
$retrievedItem = $cacheItem->get();
} else {
$retrievedItem = call_user_func($valueCallback);
if (empty($retrievedItem)) {
if ($fallback->isHit()) {
$retrievedItem = $fallback->get();
}
} else {
$cacheItem->set($retrievedItem);
if ($cacheTtl > 0) {
$cacheItem->expiresAfter($cacheTtl);
}
$cacheService->save($cacheItem);
$cacheService->save($fallback);
}
}
return $retrievedItem;
}
}
<?php
namespace spec\NMH\Helper;
use NMH\Helper\FallbackCacheItemRepository;
use NMH\SocialIntegrations\DisqusIntegration;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Prophecy\Prophet;
use Stash\Invalidation;
use Stash\Item;
use Tedivm\StashBundle\Service\CacheService;
/**
* Class FallbackCacheItemRepositorySpec
*
* @package spec\NMH\Helper
*
* @codingStandardsIgnoreStart
* @SuppressWarnings(PHPMD)
*/
class FallbackCacheItemRepositorySpec extends ObjectBehavior
{
// Constants.
private $cacheKey = 'disqus/most_discussed_articles';
private $fallbackKey = 'disqus/most_discussed_articles/fallback';
private $cacheTtl = -1;
function it_is_initializable()
{
$this->shouldHaveType(FallbackCacheItemRepository::class);
}
function it_retrieves_empty_data_from_the_service(CacheService $cacheService, DisqusIntegration $disqusIntegration)
{
// Mocks.
$prophet = new Prophet();
// Generic setup.
$cacheItem = $prophet->prophesize(Item::class);
$cacheItem->save()->willReturn(true);
$cacheItem->setInvalidationMethod(Argument::any())->willReturn();
$fallbackCacheItem = $prophet->prophesize(Item::class);
$fallbackCacheItem->save()->willReturn(true);
$fallbackCacheItem->setInvalidationMethod(Argument::any())->willReturn();
$cacheService->getItem($this->cacheKey)->willReturn($cacheItem);
$cacheService->getItem($this->fallbackKey)->willReturn($fallbackCacheItem);
// First case is, that the service returns an empty array - the was was either not successful, or the result is
// really empty.
$disqusIntegration->getMostDiscussed()->willReturn([]);
// Skipping any cache for now.
$cacheItem->isHit()->willReturn(false);
$fallbackCacheItem->isHit()->willReturn(false);
$this->getValueFromCacheWithFallback(
$this->cacheKey,
$this->fallbackKey,
$cacheService,
function () use ($disqusIntegration) {
return $disqusIntegration->getWrappedObject()->getMostDiscussed();
},
$this->cacheTtl,
Invalidation::OLD
)->shouldBe([]);
}
function it_retrieves_actual_data_from_the_service_and_stores_them_in_cache(
CacheService $cacheService,
DisqusIntegration $disqusIntegration
) {
// Mocks.
$prophet = new Prophet();
// Generic setup.
$cacheItem = $prophet->prophesize(Item::class);
$cacheItem->save()->willReturn(true);
$cacheItem->setInvalidationMethod(Argument::any())->willReturn();
$fallbackCacheItem = $prophet->prophesize(Item::class);
$fallbackCacheItem->save()->willReturn(true);
$fallbackCacheItem->setInvalidationMethod(Argument::any())->willReturn();
$cacheService->getItem($this->cacheKey)->willReturn($cacheItem);
$cacheService->getItem($this->fallbackKey)->willReturn($fallbackCacheItem);
// Skipping any cache for now.
$cacheItem->isHit()->willReturn(false);
$fallbackCacheItem->isHit()->willReturn(false);
// Now the underlying service really returns some data.
$actualValue = ['a', 'b', 'c'];
$cacheItem->set($actualValue)->shouldBeCalled();
$fallbackCacheItem->set($actualValue)->shouldBeCalled();
$cacheService->save($cacheItem)->shouldBeCalled();
$cacheService->save($fallbackCacheItem)->shouldBeCalled();
$disqusIntegration->getMostDiscussed()->willReturn($actualValue);
$this->getValueFromCacheWithFallback(
$this->cacheKey,
$this->fallbackKey,
$cacheService,
function () use ($disqusIntegration) {
return $disqusIntegration->getWrappedObject()->getMostDiscussed();
},
$this->cacheTtl,
Invalidation::OLD
)->shouldBe($actualValue);
}
function it_retrieves_data_from_fallback_cache(CacheService $cacheService, DisqusIntegration $disqusIntegration)
{
// Mocks.
$prophet = new Prophet();
// Generic setup.
$cacheItem = $prophet->prophesize(Item::class);
$cacheItem->save()->willReturn(true);
$cacheItem->setInvalidationMethod(Argument::any())->willReturn();
$fallbackCacheItem = $prophet->prophesize(Item::class);
$fallbackCacheItem->save()->willReturn(true);
$fallbackCacheItem->setInvalidationMethod(Argument::any())->willReturn();
$cacheService->getItem($this->cacheKey)->willReturn($cacheItem);
$cacheService->getItem($this->fallbackKey)->willReturn($fallbackCacheItem);
// Fallback now has some data.
$actualValue = ['a', 'b', 'c'];
$cacheItem->isHit()->willReturn(false);
$cacheItem->set($actualValue)->shouldBeCalled();
$fallbackCacheItem->isHit()->willReturn(true);
$fallbackCacheItem->get()->willReturn($actualValue);
$fallbackCacheItem->set($actualValue)->shouldBeCalled();
$cacheService->save($cacheItem)->willReturn();
$cacheService->save($fallbackCacheItem)->willReturn();
$disqusIntegration->getMostDiscussed()->willReturn([]);
$this->getValueFromCacheWithFallback(
$this->cacheKey,
$this->fallbackKey,
$cacheService,
function () use ($disqusIntegration) {
return $disqusIntegration->getWrappedObject()->getMostDiscussed();
},
$this->cacheTtl,
Invalidation::OLD
)->shouldBe($actualValue);
}
function it_retrieves_data_from_the_cache(
CacheService $cacheService,
DisqusIntegration $disqusIntegration,
Item $cacheItem,
Item $fallbackCacheItem
) {
$cacheItem->save()->willReturn(true);
$cacheItem->setInvalidationMethod(Invalidation::OLD)->shouldBeCalled();
$cacheService->getItem($this->cacheKey)->willReturn($cacheItem);
$cacheService->getItem($this->fallbackKey)->willReturn($fallbackCacheItem);
// Cache now has some data.
$cacheItem->isHit()->willReturn(true);
$actualValue = ['a', 'b', 'c'];
$cacheItem->get()->willReturn($actualValue);
$this->getValueFromCacheWithFallback(
$this->cacheKey,
$this->fallbackKey,
$cacheService,
function () use ($disqusIntegration) {
return $disqusIntegration->getWrappedObject()->getMostDiscussed();
},
$this->cacheTtl,
Invalidation::OLD
)->shouldBe($actualValue);
}
function it_sets_cache_life_span(
CacheService $cacheService,
DisqusIntegration $disqusIntegration,
Item $cacheItem,
Item $fallbackCacheItem
) {
$cacheItem->save()->willReturn(true);
$cacheItem->setInvalidationMethod(Invalidation::OLD)->shouldBeCalled();
$cacheItem->expiresAfter(Argument::type('int'))->willReturn();
$cacheService->getItem($this->cacheKey)->willReturn($cacheItem);
$cacheService->getItem($this->fallbackKey)->willReturn($fallbackCacheItem);
$cacheService->save($cacheItem)->willReturn();
$cacheService->save($fallbackCacheItem)->willReturn();
// Current cache is stale, new one needs to be set with live span.
$cacheItem->isHit()->willReturn(false);
$actualValue = ['a', 'b', 'c'];
$cacheItem->set($actualValue)->shouldBeCalled();
$disqusIntegration->getMostDiscussed()->willReturn($actualValue);
$this->getValueFromCacheWithFallback(
$this->cacheKey,
$this->fallbackKey,
$cacheService,
function () use ($disqusIntegration) {
return $disqusIntegration->getWrappedObject()->getMostDiscussed();
},
10,
Invalidation::OLD
)->shouldBe($actualValue);
$cacheItem->expiresAfter(10)->shouldHaveBeenCalled();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment