Here’s a very simply dependency which helps you to remove specific items from the cache. It does not require another query to find out if your record has changed. Instead it just uses another cache key to inform the dependency about the change.
I’ve kept it very simple, so you use it like this:
<?php
// At the query:
$dependency = new FlushableDependency($id,'Post');
$post = Post::model()->cache(3600,$dependency)->findByPk($id);
// In the model:
FlushableDependency::flushItem($this->id,'Post');
Maybe i’ll add a behavior to the extension which you can attach to your ActiveRecords to automate this. But i’m hesitating because it may not catch all updates (e.g. calls to saveAttributes()).
I think this is a really great idea, especially in combination with afterSave() etc. and will take a lot of load off the db.
Regarding your idea of making this a behavior: You think newcomers would easily get “tricked” by thinking saveAttributes() calls afterSave() which it doesn’t? I agree that this is somewhat weird but wouldn’t they have the same problem implementing this dependency by themselves?
Exactly. We should have events for this. I hope we get them in Yii 2.0.
Not sure what you mean. But if we don’t automate too much, there’s a better chance that the developer understands what this dependency does. So he will know, that whenever he updates (either by save() or saveAttributes()) or deletes a record, he also has to flush the respective ids from the cache through this dependency.
Bethrenzen raised the valid argument, that this dependency could also be used for page/fragment caching. In this case the $model does not make sense. So i changed the interface slightly and realeased version 1.1.0.
I hope such an interface change is still acceptable on the first day
You would have to add another flushable dependency to your COutputCache. But this only makes sense if Post is the only cached AR on that page and if you know the id.
You only should add the dependency if you use a cache() query. So in your example, you don’t have to replace
That is one dependency for each model (i have more or less 50 models for now)?
Is a cOutputCache filter in general controller (Controller that extends CController) the best approach? Or the best approach is define cOutputCache filter to each controller?
Bottom line, i want to cache all pages with cOutputCache and always that a model is updated, cache should be updated too.
You can not have more than one COutputCache for the same page. So either configure your filter to be active for specific pages only or take another approach. Apart from that i can’t see, where you use the flushable behavior at all.
To solve my problem I followed your advice and tried a new approach to develop my caching system and improve page loading.
I used COutputCache filter on specific pages with a dependency to renew cache as the following example.
class ProviderController extends Controller
{
...
public function filters()
{
return array(
'accessControl', // perform access control for CRUD operations
array(
'COutputCache +navigation',
'duration'=>7200,
'dependency'=>array(
'class'=>'system.caching.dependencies.CDbCacheDependency',
'sql'=>'SELECT MAX(update_date) FROM tb_provider'
)
)
);
}
...
}
Beyond the pages, I also needed to cache fragments of some pages. These fragments depended on some ActiveRecords. For that propose, used the flushable extension that facilitate this process.
For each ActiveRecord used on a fragment caching I override afterSave method to flush the content from cache as following code.
Provider ActiveRecord example:
class Provider extends CActiveRecord
{
...
public function afterSave()
{
FlushableDependency::flushItem('Provider');
return parent::afterSave();
}
...
}
And on the fragment page which depends on Property ActiveRecord I used the following code:
Much better . But you should include the provider id in the calls to flushItem($this->id,‘Provider’) and FlushableDependency($id, ‘Provider’). Otherwhise whenever you update one provider you flush all from the cache. You’ll probably use something like $id = isset($_GET[‘id’]) ? (int) $_GET[‘id’] : -1; or something to get the $id in the controller.