Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine 2 and ORM: how to cache every query for some entity?

I'm fighting with this problem a lot of time and found, that officially, I could cache only some custom queries (useResultCache(true) on Query object). But I need to cache every query in my application to some table. What about find* methods on EntityManager?...

Could somebody help me to find an elegant solution?

like image 770
Andrii Vasyliev Avatar asked Mar 27 '13 14:03

Andrii Vasyliev


People also ask

Does ORM cache?

The Doctrine ORM package can leverage cache adapters implementing the PSR-6 standard to allow you to improve the performance of various aspects of Doctrine by simply making some additional configurations and method calls.

What is doctrine cache?

Doctrine Cache is a library that provides an interface for caching data.

How Hibernate second level cache works?

This cache only works at a session level, meaning each session object caches data independently, so there is no sharing of cached data across sessions, and the cached data is deleted when the session closes. This makes the cache only useful for repeated queries in the same session.


1 Answers

This is not yet supported, and you should eventually handle it in your service layer or in your extended repositories.

What you are looking for is the second level cache as in Hibernate, which basically allows you to plug in a key-value store such as redis, riak, mongodb, etc to make things blazing fast when operations are simple fetch operations.

There's a work in progress pull request at https://github.com/doctrine/doctrine2/pull/580, which will probably land in Doctrine ORM 2.5, so please review that one.

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\ArrayCache;

class MyBaseRepo extends EntityRepository
{
    public function __construct(EntityManager $entityManager)
    {
        parent::__construct($entityManager);
        $cache       = $em->getConfiguration()->getHydrationCache();
        $this->cache = $cache ?: new ArrayCache();
    }

    public function find($id)
    {
        if (!$object = $this->tryFetchFromCache($id)) {
            $object = parent::find($id);
            $this->cache->save($this->generateKey($id), $object);
        }

        return $object;
    }

    protected function tryFetchFromCache($id)
    {
        if (!$object = $this->cache->fetch($this->generateCacheKey($id))) {
            return null;
        }

        return $this->getEntityManager()->merge($object);
    }

    public function generateCacheKey($id) { /* ... */ }
}

You can force this to be the base repository for the ORM in your configuration while bootstrapping your app:

$configuration = new \Doctrine\ORM\Configuration();

$configuration->setDefaultRepositoryClassName('My\MyBaseRepo');

This also forces you to purge cache entries when an update/save happens for any of your entities:

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;

class IssueUpdateSubscriber implements EventSubscriber
{
    public function onFlush(OnFlushEventArgs $args)
    {
        $em  = $args->getEntityManager();
        $uow = $em->getUnitOfWork();

        if (!$cache = $em->getConfiguration()->getHydrationCache()) {
            return;
        }

        $busted = array_merge(
            $uow->getScheduledEntityUpdates(),
            $uow->getScheduledEntityDeletions(),
        );

        foreach ($busted as $entityToClear) {
            $className  = get_class($entityToClear);
            $metadata   = $em->getClassMetadata($className);
            $repository = $em->getRepository($className);
            $id         = $metadata->getIdentifierValues($entityToClear);

            $cache->delete($repository->generateCacheKey($id));
        }
    }

    public function getSubscribedEvents()
    {
        return array(Events::onFlush);
    }
}

Please note that this implementation does NOT intercept all accesses to your database for a given entity. It will not intercept lazy loading initialization caused by proxies, and it is very fragile, so please setup some proper integration tests to support it.

like image 197
Ocramius Avatar answered Oct 06 '22 22:10

Ocramius