Skip to content

[PATCH] Add ID hashing to beginCache() to auto-invalidate caches #1968

Closed
borgand opened this Issue Jan 13, 2013 · 9 comments

4 participants

@borgand
borgand commented Jan 13, 2013

With the following patch one can supply an arbitrary object as the cache ID, making the cache-key autoinvalidate when the object changes:

diff --git a/yiiroot/framework/web/CBaseController.php b/yiiroot/framework/web/CBaseController.php
index bfa0937..c4a5055 100644
--- a/yiiroot/framework/web/CBaseController.php
+++ b/yiiroot/framework/web/CBaseController.php
@@ -252,7 +252,7 @@ abstract class CBaseController extends CComponent
         */
        public function beginCache($id,$properties=array())
        {
-               $properties['id']=$id;
+               $properties['id']=sha1(print_r($id, true));
                $cache=$this->beginWidget('COutputCache',$properties);
                if($cache->getIsContentCached())
                {

And using this in code:

if ($this->beginCache($model)) {

The cache will be automatically invalidated when the object in question changes. No need to specify duration or depencencies.

This also allows using an array of components for a cache:

if ($this->beginCache(array($current_user, $model, 'buttons'))) {

Or the like.

@qiangxue
Yii Software LLC member

Why not pass the parameter directly as sha1(print_r(...))? For most output cache usage, doing sha1(print_r()) is too much.

@qiangxue qiangxue closed this Jan 13, 2013
@borgand
borgand commented Jan 14, 2013

Because it is tedious to write sha1(print_r(...)) all over the place - clutters up the views etc. This is one line in framework but tens of lines in apps.

If sha1 is overkill for most cases, then could this be added instead:

$properties['id']=is_string($id) ? $id : sha1(print_r($id, true))

This preserves the old way for strings and gives sha1 for new usage.

@qiangxue
Yii Software LLC member

Because you modify the original id value, it looks more like a hack. If you use this widely, you can overwrite the beginCache() method.

@borgand
borgand commented Jan 14, 2013

Thanks for replying.

I just thought that it would be beneficial as a core feature for simplifying the cache dependencies system.

I might turn this into and extension someday. Until then, yes, I will overwrite the method for me.

@DaSourcerer

Aside from everything else ... is print_r() really suitable for this? One would think that serialize() might be way better for this.

@borgand
borgand commented Jan 18, 2013

I compared print_r() and serialize() in a quick benchmark, computing 5 000 000 times:

md5(print_r($m,true))

or

md5(serialize($m))

where $m is an object from ActiveRecord model. print_r() turned out to be roughly 30% faster (46 sec vs 64 sec respectively).

As we only need one-way serialization, then print_r seems appropriate.

Note however, that I'm not an expert in PHP or ActiveRecord internals and as documented, serialize() tries to call __sleep() on the object to clean up itself before serializing it, so the represented state of the object might be a tad bit different from that of print_r(), but I suspect that it wont matter in this case.

@samdark
Yii Software LLC member
samdark commented Jan 18, 2013

@borgand try json_encode. Should be even faster.

@borgand
borgand commented Jan 18, 2013

@samdark thanks for the pointer.
It is indeed quite a lot faster, but unfortunately json_encode($m) outputs {} and I would have to use $m->attributes to get a result and that would mean it is not universal for whatever is input as$id.

I found that even CJSON::encode() is faster than print_r() (though times slower than json_encode()).
I will contemplate on it for a while and see which which one I will go for.

@borgand
borgand commented Jan 18, 2013

This kind of function could be as fast as it gets while keeping the $id still readable (for logging purposes etc):

<?php

function getHashedID($id_data){
  // Scalar types go as-is
  if(is_scalar($id_data)){
    // Scalar types first
    return (string)$id_data;
  }
  // Arrays are mapped recursively and joined with "/"
  elseif (is_array($id_data)) {
    return join("/", array_map('getHashedID', $id_data));
  }
  // CModels are mapped to sha1 of their attributes
  elseif (is_object($id_data) && in_array('CModel',class_parents(get_class($id_data)))) {
    return get_class($id_data) . "-" . sha1(json_encode($id_data->attributes));
  }
  // json_encode won't handle non-iterateable objects
  // use print_r for those
  elseif (is_object($id_data)) {
    return get_class($id_data) . "-" . sha1(print_r($id_data, true));
  }
  // Everything else is sha1 of their print_r output
  else {
    return sha1(print_r($id_data, true));
  }
}

It would work like this:

>> echo getHashedID(1);
1
>> echo getHashedID('test');
test
>> echo getHashedID($company);
Company-35021e0f536489d6479a2374150685daf5007a0f
>> echo getHashedID(array($user, $company, 'buttons'));
User-a34c7ec7843f3ede60f89a56ab78067f35c8aa59/Company-35021e0f536489d6479a2374150685daf5007a0f/buttons
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.