This one is a little tricky to explain, so I created a sample project and will explain using that example. In a nutshell, the issue here is that queries are saved to cache with the wrong cache dependency when (and only when) using a ‘with’ clause in criteria.
The project contains a simple db with a couple of book names (table ‘book’) and their respective authors (table ‘author’). There are only two models: Book and Author. There is also a simple controller that creates a CActiveDataProvider instance for Book and specifies a ‘with’ relation (‘author’) in criteria.
Here’s how to create the sample project:
-
Start with a new yii webapp.
-
Set up CFileCache in config (or use the attached main.php config file)
-
Import the attached db schema (MySQL) and set up yii to use this db.
-
Set up query caching and use extremely high values, eg:
'queryCachingDuration' => 365 * 86400,
'queryCachingCount' => PHP_INT_MAX,
-
Create models for the two tables using gii (or use the attached files Book.php and Author.php).
-
Override getDbConnection() in both models, like so:
public function getDbConnection()
{
$db = parent::getDbConnection();
$db->queryCachingDependency = new CGlobalStateCacheDependency(__CLASS__ . 'CacheDependency');
return $db;
}
(The purpose of this method is to create the appropriate cache dependency for each model. It will create an ‘AuthorCacheDependency’ for Author and ‘BookCacheDependency’ for Book.)
- Create an action that instantiates the data provider and renders the view (see attached SiteController):
public function actionIndex()
{
$dataProvider = new CActiveDataProvider('Book', array(
'criteria' => array(
'with' => 'author'
)
));
$this->render('index', array('dataProvider' => $dataProvider));
}
- All you need in the view file is this:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => $dataProvider,
));
That’s it, the project is ready. Now, to reproduce the bug, simply open the page in the browser and then check the cached data in runtime/cache. One of the cache files will contain the following (I removed some non-printable characters):
a:2:{i:0;a:5:{i:0;a:5:{s:5:"t0_c0";s:1:"1";s:5:"t0_c1";s:1:"1";s:5:"t0_c2";s:23:"The Catcher in the Rye ";s:5:"t1_c0";s:1:"1";s:5:"t1_c1";s:14:"J. D. Salinger";}i:1;a:5:{s:5:"t0_c0";s:1:"2";s:5:"t0_c1";s:1:"1";s:5:"t0_c2";s:16:"Franny and Zooey";s:5:"t1_c0";s:1:"1";s:5:"t1_c1";s:14:"J. D. Salinger";}i:2;a:5:{s:5:"t0_c0";s:1:"3";s:5:"t0_c1";s:1:"2";s:5:"t0_c2";s:12:"The Stranger";s:5:"t1_c0";s:1:"2";s:5:"t1_c1";s:12:"Albert Camus";}i:3;a:5:{s:5:"t0_c0";s:1:"4";s:5:"t0_c1";s:1:"3";s:5:"t0_c2";s:20:"Crime and Punishment";s:5:"t1_c0";s:1:"3";s:5:"t1_c1";s:18:"Fyodor Dostoyevsky";}i:4;a:5:{s:5:"t0_c0";s:1:"5";s:5:"t0_c1";s:1:"3";s:5:"t0_c2";s:22:"Notes from Underground";s:5:"t1_c0";s:1:"3";s:5:"t1_c1";s:18:"Fyodor Dostoyevsky";}}i:1;O:27:"CGlobalStateCacheDependency":4:{s:9:"stateName";s:21:"AuthorCacheDependency";s:23:"CCacheDependency_data";N;s:14:"CComponent_e";N;s:14:"CComponent_m";N;}}
As you can see (scroll to the right), the dependency for this cache entry is ‘AuthorCacheDependency’, and it should be (I believe) ‘BookCacheDependency’, since we are querying Book model and only joining Author model. If you clear the cache and retry without specifying the ‘with’ relation, the correct dependency is used. The upshot is that you can’t rely on this dependency to invalidate cached data.
Hope I managed to explain it properly.
Cheers!