Deleting doctrine memcache on create/edit/delete in symfony2

I’m using Doctrine memcache (not memcacheD) in my Symfony 2.7 application. Here is my configuration:

doctrine:
    orm:
        metadata_cache_driver: memcache
        result_cache_driver: memcache
        query_cache_driver: memcache

I’m trying to delete the respective cache items on entity create/edit/delete. As memcache doesn’t support deletion using prefix, I did not find an easy solution to delete certain cache items of an entity create/edit/delete. Let’s say I have a Property entity, I want to delete all caches related to Property on Property create/edit/delete. (I believe deleteAll() is not a good idea in this case)

Deleting using namespace could be a workaround, but I think namespace can only be set on the cache driver, not on a particular entity or repository. How could it be possible to distinguish the namespaces for each entity?

So, I’m now invalidating the result cache using onFlush event (This is the only way I found and the article is too old back in 2012. Is there any better recent solution?). One drawback of that solution is to define each cache id for each query and define all of them in the service class CacheInvalidator such as

namespace MyBundle\Listener\Event;
// ...

class CacheInvalidator
{
    //...

    protected function getCacheIds()
    {
        return array(
            'MyBundle\Entity\BlogPost' => array(
                array(
                    'id' => 'posts_for_user_%d',
                    'vars' => array(
                        array(
                            'value' => 'getUser',
                            'type' => 'method',
                        ),
                    ),
                    'change' => 'any',
                ),
            ),
            // Add more entities etc here
        );
    }
}

The article author gave a very simple example using the cache id posts_for_user_%d. It is fine using such simple query. The problem is that I have many queries using a lot of dynamic query parameters. For example, I have the following method in Property repository which can be called with various options:

public function findRecommendations($options = null)
{
    if (isset($options['limit'])) {
        $limit = $options['limit'];
    } elseif (is_numeric($options)) {
        $limit = $options;
    } else {
        $limit = null;
    }

    $qb = $this->createQueryBuilder('p');

    $qb->where('p.recommend_flag = :recommend_flag');
    $qb->setParameter('recommend_flag', 1);
    $qb->andWhere('DATE(p.expired_at) > :now');
    $qb->setParameter('now', date('Y-m-d'));

    $resultCacheParams = array(
        'type'  => array('%d', '%s'),
        'value' => array(1, date('Ymd'))
    );

    if ($limit) {
        $qb->setMaxResults($limit);
        $resultCacheParams['type'][] = 'limit%d';
        $resultCacheParams['value'][] = $limit;
    }

    $resultCacheId = vsprintf(
        'Property_findRecommendations_'.implode('_', $resultCacheParams['type']),
        $resultCacheParams['value']
    );

    $query = $qb->getQuery();

    $query->useQueryCache(true);
    $query->setQueryCacheLifetime(21600);
    $query->useResultCache(true, 21600, $resultCacheId);

    return $query->getResult();
}

So, I have to define two combination of cache id in the service class – Property_findRecommendations_%d_%s and Property_findRecommendations_%d_%s_limit%d, one using limit and the other without limit.

protected function getCacheIds()
{
    return array(
        'MyBundle\Entity\Property' => array(
                'id' => 'Property_findRecommendations_%d_%s',
                'vars' => array(
                    array('value' => 1), // recommend_flag
                    array('value' => date('Ymd')),
                ),
                'change' => 'any'
            ),
            array(
                'id' => 'Property_findRecommendations_%d_%s_limit%d',
                'vars' => array(
                    array('value' => 1), // recommend_flag
                    array('value' => date('Ymd')),
                    array('value' => $this->container->getParameter('max_recommend_properties'))
                ),
                'change' => 'any'
            ),
    );
}

This is just for one parameter as an example. There will be more than one parameters to the method. More parameters, more combinations of cache id definitions in the service class. It is really expensive I think.

The worst case is query with pagination that accepts dynamic page parameter. Each query result for each page should be cached using different cache id, for example Property_getAllProperties_%d

Property_getAllProperties_1 // for page 1
Property_getAllProperties_2 // for page 2
Property_getAllProperties_3 // for page 3
....

I can’t know the current page number in the method getCacheIds() of the service class. Should I calculate the number of pages of all properties and define array of cache Ids dynamically in the method getCacheIds()?

I feel that it is something weird and seems like a workaround. In addition, I have the search queries that depends on various parameters. I feel that they are impossible to define in getCacheIds().

Is there a better and standard way to invalidate doctrine memcache result cache?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s