Yii supports many cache backends, but what really makes Yii cache flexible is the dependency chaining support. There are situations when you cannot just simply cache data for an hour because the information cached can be changed at any time.
In this recipe, we will see how to cache a whole page and still always get fresh data when it is updated. The page will be dashboard type and will show the five latest articles added and a total calculated for an account. Note that an operation cannot be edited as it was added, but an article can.
yiic webapp
.components
section of protected/config/main.php
as follows:'cache' => array( 'class' => 'CApcCache', ),
CREATE TABLE `account` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`amount` decimal(10,2) NOT NULL,PRIMARY KEY (`id`)); CREATE TABLE `article` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`title` varchar(255) NOT NULL,`text` text NOT NULL,PRIMARY KEY (`id`));
account
and article
tables using Gii.db
and log
application components through protected/config/main.php
, so we can see actual database queries. In the end, the config for these components should look like the following:'db'=>array( 'connectionString' => 'mysql:host=localhost;dbname=test', 'username' => 'root', 'password' => '', 'charset' => 'utf8', 'schemaCachingDuration' => 180, 'enableProfiling'=>true, 'enableParamLogging' => true, ), 'log'=>array( 'class'=>'CLogRouter', 'routes'=>array( array( 'class'=>'CProfileLogRoute', ), ), ),
protected/controllers/DashboardController.php
as follows:<?php class DashboardController extends CController { public function actionIndex() { $db = Account::model()->getDbConnection(); $total = $db->createCommand("SELECT SUM(amount) FROM account")->queryScalar(); $criteria = new CDbCriteria(); $criteria->order = "id DESC"; $criteria->limit = 5; $articles = Article::model()->findAll($criteria); $this->render('index', array( 'total' => $total, 'articles' => $articles, )); } public function actionRandomOperation() { $rec = new Account(); $rec->amount = rand(-1000, 1000); $rec->save(); echo "OK"; } public function actionRandomArticle() { $n = rand(0, 1000); $article = new Article(); $article->title = "Title #".$n; $article->text = "Text #".$n; $article->save(); echo "OK"; } }
protected/views/dashboard/index.php
as follows:<h2>Total: <?php echo $total?></h2> <h2>5 latest articles:</h2> <?php foreach($articles as $article):?> <h3><?php echo $article->title?></h3> <div><?php echo $article->text?></div> <?php endforeach ?>
dashboard/randomOperation
and dashboard/randomArticle
several times. Then, run dashboard/index
and you should see a screen similar to the one shown in the following screenshot:Carry out the following steps:
class DashboardController extends CController { public function filters() { return array( array( 'COutputCache +index', // will expire in a year 'duration'=>24*3600*365, 'dependency'=>array( 'class'=>'CChainedCacheDependency', 'dependencies'=>array( new CGlobalStateCacheDependency('article'), new CDbCacheDependency('SELECT id FROM account ORDER BY id DESC LIMIT 1'), ), ), ), ); } public function actionIndex() { $db = Account::model()->getDbConnection(); $total = $db->createCommand("SELECT SUM(amount) FROM account")->queryScalar(); $criteria = new CDbCriteria(); $criteria->order = "id DESC"; $criteria->limit = 5; $articles = Article::model()->findAll($criteria); $this->render('index', array( 'total' => $total, 'articles' => $articles, )); } public function actionRandomOperation() { $rec = new Account(); $rec->amount = rand(-1000, 1000); $rec->save(); echo "OK"; } public function actionRandomArticle() { $n = rand(0, 1000); $article = new Article(); $article->title = "Title #".$n; $article->text = "Text #".$n; $article->save(); Yii::app()->setGlobalState('article', $article->id); echo "OK"; } }
dashboard/index
several times, you will get only one simple query, as shown in the following screenshot:Also, try to run either dashboard/randomOperation
or dashboard/randomArticle
and refresh dashboard/index
after that. The data should change.
In order to achieve maximum performance while doing minimal code modification, we use a full-page cache by using a filter as follows:
public function filters() { return array( array( 'COutputCache +index', // will expire in a year 'duration'=>24*3600*365, 'dependency'=>array( 'class'=>'CChainedCacheDependency', 'dependencies'=>array( new CGlobalStateCacheDependency('article'), new CDbCacheDependency('SELECT id FROM account ORDER BY id DESC LIMIT 1'), ), ), ), ); }
The preceding code means that we apply full-page cache to the index
action. The page will be cached for a year and the cache will refresh if any of the dependency data changes. Therefore, in general, the dependency works as follows:
In our case, two dependency types are used: global state and DB. Global state dependency uses data from Yii::app()->getGlobalState()
to decide if we need to invalidate the cache while DB dependency uses the SQL query result for the same purpose.
The question that you have now is probably, "Why have we used DB for one case and global state for another?" That is a good question!
The goal of using the DB dependency is to replace heavy calculations and select a light query that gets as little data as possible. The best thing about this type of dependency is that we don't need to embed any additional logic in the existing code. In our case, we can use this type of dependency for account operations, but cannot use it for articles as the article content can be changed. Therefore, for articles, we set a global state named article
to the current time, which basically means that we are scheduling cache invalidation:
Yii::app()->setGlobalState('article', time());
3.16.66.156