flushable

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.

http://www.yiiframew…sion/flushable/

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()).

Let me know, what you think about it.

EDIT:

Updated the examples for 1.1.0.

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.

That’s what I tried to say although I can’t even get that form my own words now ;)

Less automation will make it easier to maintain I think

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 :)

Mike, can you please upload this extension to github? Thanks.

Ok, i’ll move it to github over the next days.

I’m working in a semi-static website project based in Yii framework.

At this moment i want to implement a cache to improve performance and page on load.

I’m using COutputCache filter on Controller (that extends CController) to cache all pages (static and dynamic).




public function filters()

	{

		return array(

			array(

				'COutputCache',

				'duration'=>20,

			)

		);

	}

	



As I have several zones with dynamic data updated regularly by members,

Flushable extension seems to be an excellent solution but some problems came up.

On all models, i flush data on afterSave method.




public function afterSave()

	{

		FlushableDependency::flushItem($this->id,'Post');

		return parent::afterSave();

	}



Now, when i want to get the fresh data, cOutputCache override any results (parent cache duration bigger)




$data = Post::model()->findByPk($id);



Is it necessary to use renderDynamic?

second with this extension will it be necessary to replace the above code by the following code in every controllers/actions?




// Cache a ActiveRecord

$dependency = new FlushableDependency($id,'Post');

$post = Post::model()->cache(10,$dependency)->findByPk($id);



I need help here.

Thank you in advance.

  1. 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.

  2. You only should add the dependency if you use a cache() query. So in your example, you don’t have to replace


$data = Post::model()->findByPk($id);

Thank you for your quick answer.

If i replace Controller filters to the following code, does it work?




public function filters()

        {

                return array(

                        array(

                                'COutputCache',

                                'duration'=>20,

                                'varyByParam' => array('id'),

                                'dependency' => array(

					'class'=>'CDbCacheDependency',

    				        'sql'=>'SELECT MAX(modified) FROM tb_prov',

                                 )

                        ),

                        array(

                                'COutputCache',

                                'duration'=>20,

                                'varyByParam' => array('id'),

                                'dependency' => array(

					'class'=>'CDbCacheDependency',

    				        'sql'=>'SELECT MAX(modified) FROM tb_prop',

                                 )

                        ),

                        array(

                                'COutputCache',

                                'duration'=>20,

                                'varyByParam' => array('id'),

                                'dependency' => array(

					'class'=>'CDbCacheDependency',

    				        'sql'=>'SELECT MAX(modified) FROM tb_mem',

                                 )

                        )

                );

        }




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.

Thank you a lot for your help.

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.

Thank you for your help.

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:




<?php

$dependency = new FlushableDependency('Provider');

if($this->beginCache('memberbenefit_widget', array('duration'=>7200, 'dependency'=>$dependency)))

{ 

?>

...HTML code...

<?php 

	$this->endCache();

}

?>



I don’t know if this is the best solution but it seems to work.

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.