1

I have many methods in many repositories that have the same logical flow:

  1. Retrieve value from Cache
  2. Check value
    1. If no value, fetch from Database
    2. Update Cache
  3. Return value

Is there a way to refactor these methods in such a way that I don't have to rewrite the same lines over and over again?

I'm using an MVC pattern with services e.g. ActivityService service has ActivityRepositoryInterface (most likely it will be the concrete cached version you see below) injected. I can paste more code if it helps?

Thanks

namespace DW\DWBundle\Repository\Cached;

use DW\DWBundle\Cache\ActivityCache;
use DW\DWBundle\Entity\User;
use DW\DWBundle\Helper\CacheHelper;
use DW\DWBundle\Repository\ActivityRepository as ActivityRepositoryInterface;
use DW\DWBundle\Repository\Doctrine\ORM\ActivityRepository as ActivityRepositoryDoctrine;

class ActivityRepository extends CustomRepository implements ActivityRepositoryInterface
{
    private $activityRepository;
    private $cacheHelper;

    public function __construct(ActivityRepositoryDoctrine $activityRepository, CacheHelper $cacheHelper)
    {
        parent::__construct($activityRepository);

        $this->activityRepository = $activityRepository;
        $this->cacheHelper = $cacheHelper;
    }

    public function findRecentLikeActivity($limit)
    {
        $key = ActivityCache::KEY_LIKES_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $classType = "ArrayCollection<DW\DWBundle\Entity\Activity>";
        $activity = $this->cacheHelper->getFromCache($key, $name, $classType);
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentLikeActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findRecentCommentActivity($limit)
    {
        $key = ActivityCache::KEY_COMMENTS_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentCommentActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findRecentActivity($limit)
    {
        $key = ActivityCache::KEY_ALL_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityByUser(User $user)
    {
        $key = ActivityCache::KEY_USER_ID."_".$user->getId();
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityByUser($user);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityOrderedByCreated()
    {
        $key = ActivityCache::KEY_LIST;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityOrderedByCreated();
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityOrderedByCreatedASC()
    {
        $key = ActivityCache::KEY_LIST_ASC;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityOrderedByCreatedASC();
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }
}

....

namespace DW\DWBundle\Helper;

use JMS\Serializer\Serializer;
use Tbbc\CacheBundle\Cache\CacheManagerInterface;
use Tbbc\CacheBundle\Cache\KeyGenerator\KeyGeneratorInterface;

class CacheHelper
{
    private $cacheManager;
    private $keyGenerator;
    private $serializer;

    public function __construct(CacheManagerInterface $cacheManager,
                                KeyGeneratorInterface $keyGenerator,
                                Serializer $serializer)
    {
        $this->cacheManager = $cacheManager;
        $this->keyGenerator = $keyGenerator;
        $this->serializer = $serializer;
    }

    public function getFromCache($key, $name, $classType)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $serialized = $cache->get($cacheKey);
        $deserialized = $this->deserialize($serialized, $classType);
        return $deserialized;
    }

    public function saveToCache($key, $name, $value)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $serialized = $this->serialize($value);
        $cache->set($cacheKey, $serialized);
    }

    public function deleteFromCache($key, $name)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $cache->delete($cacheKey);
    }

    public function serialize($object)
    {
        return $this->serializer->serialize($object, 'json');
    }

    public function deserialize($data, $class)
    {
        return $this->serializer->deserialize($data, $class, 'json');
    }
}
3

You could create a third class that manages both the repository and the cache. Then, don't manually try to get the cached value and if not available query the repository like

$activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
    if ($activity == null) {
        $activity = $this->activityRepository->findRecentCommentActivity($limit);
        $this->cacheHelper->saveToCache($key, $name, $activity);
    }

Rather have your CachedRepository answer it:

$activity = $this->cachedRepository->getFromCacheOrRepository($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");

The cache and repository query logic can now be implemented in the CachedRepository class and therefore be reused.

3
  • That's a good idea actually and it'll allow me to test the CachedRepository independent of the database. – Jonathan Jan 5 '15 at 22:39
  • So just have the CachedRepository return from the cache and the DatabaseRepository return from the database and then the third "BothRepository" do the logic above? – Jonathan Jan 5 '15 at 22:41
  • Exactly, the "BothRepository" knows both a repository and some cache. It then is responsible for getting the values either from the cache or (if it is not there) from the repository - and can fill the cache. It should also implement the RepositoryInterface because it behaves like a repository, while under the hood using caching. But that implementation detail should be not of concern by the caller. – valenterry Jan 6 '15 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.