yii中缓存(cache)详解 - 彼岸あ年華ツ
缓存是用于提升网站性能的一种即简单又有效的途径。通过存储相对静态的数据至缓存以备所需,我们可以省去生成
这些数据的时间。在 Yii 中使用缓存主要包括配置和访问缓存组件 。
内部方法
一、缓存配置:
1、单一缓存组件配置:
Yii缓存可以在不同的级别使用。在最低级别,可用来缓存单个数据(数据缓存)。往上一级,我们缓存一个由视图脚本生成的页面片断(片段缓存)。在最高级别,可存储整个页面以便需要的时候直接从缓存读取。本文说明页面缓存的配置及实现效果;
实现分为2步;
1. 在config文件加入缓存组件.
‘cache’ => array (
‘class’ => ‘system.caching.CFileCache’,
‘directoryLevel’ => 2,
),
class标识需要使用的缓存媒介,用途比较广的类型基本都有支持:
CMemCache: 使用 PHP memcache 扩展.
CApcCache: 使用 PHP APC 扩展.
CDbCache: 使用一张数据库表来存储缓存数据。
CFileCache: 使用文件来存储缓存数据。 特别适用于大块数据(例如页面)。
当然,yii也可以支持Redis,需要装一个插件:
http://www.yiibase.com/download/view/32.html
本文实例使用的是文件缓存,对于文件缓存,缓存到的位置为protected/runtime/;directoryLevel设置缓存文件的目录深度;如果缓存页面特别多,这个值需要设置大点,否则每个目录下的页面会很多;
对于除class其他的选项的配置,可以查看各class类中的一些属性
2. 在要做缓存的控制器里定义过滤器。
public function filters() {
return array (
array (
‘COutputCache + post, list’,
‘duration’ => 3600,
‘varyByParam’ => array(‘id’,'page’),
‘dependency’ => array(
‘class’=>’CDbCacheDependency’,
‘sql’=>’SELECT MAX(id) FROM me115_book’,
)
);
}
COutputCache 是用于处理缓存的类,如果只填’COutputCache’,则控制器里所有action都会通过缓存过滤,定义’COutputCache + post, list’,表示只对以下方法进行缓存:actionPost, actionList
duration 是缓存的时间,单位是秒,
varyByParam 是指定一系列GET参数名称列表, 使用相应的值去确定缓存内容的版本,即同一个action用于区分是不同页面的的参数,此处我以id和page来区分不同页面。
除varyByParam以外,还可以采用其他的条件来区分页面:
varyByExpression:指定缓存内容通过自定义的PHP表达式的结果而变化
varyByRoute:指定缓存内容基于请求的路由不同而变化 (controller 和 action)
varyBySession:指定是否缓存内容. 因用户session不同而变化
dependency’指定缓存失效依赖关系:可指定文件或数据库;本文采用的是数据库依赖CDbCacheDependency;
本例指定的是数据库,通过数据表的某个值的变化来确定缓存是否失效。例如,如果在表中新增了一条me115_book记录,即使缓存才过了2分钟(<3600),仍然判断为失效,从而查询数据库,生成整个页面,再次缓存;
检查:
查看当前页面是否缓存,可以dump输出一个当前服务器时间,从而检查当前页面是否已缓存;
优化效果:
优化站点为一个博客站点(me115.com),除了DNS解析转接外,未进行任何优化,优化前的数据为:
首字节时间为842ms;
采用页面缓存之后的效果:
首字节时间为376ms;html生成的时间大大缩短,后台时间减少了一倍。
当然,通过本图可以看到整个站点的用时还是比较长,主要是在页面组件(css/js/图片)上的下载耗费了不少时间,后续将针对这方面进行前端优化;
2、多缓存组件配置:
详看:http://hudeyong926.iteye.com/blog/1313713
//不管是多缓存配置还是单缓存配置,都需要设置一个默认的cache组件,因为CDbConnection中的 schemaCacheID ,queryCacheID,CCacheHttpSession 中的cacheID ,CUrlManager 中的cacheID, CStatePersister 中的cacheID都是默认的 ’cache'
'cache' => array(
'class' => 'CFileCache',
),
'ApcCache'=>array(
'class'=>'CApcCache',
),
'Dbcache'=>array(
'class'=>'CDbCache',
),
'FileCache'=>array(
'class'=>'CFileCache',
'cachePath'=> '/Webroot/trackstar/protected/runtime/cache/test',
),
'MemCache'=>array(
'class'=>'CMemCache',
'servers'=>array(
array(
'host'=>'server1',
'port'=>11211,
'weight'=>60,
),
array(
'host'=>'server2',
'port'=>11211,
'weight'=>40,
),
),
),
session cache在开启apc cache时可以用,它将seesion存到apc中比存到文件中要快
'cache' => array(
'class' => 'CApcCache',
),
'session' => array(
'class' => 'CCacheHttpSession', //CCacheHttpSession在system.web包里
),
yii如果要使用session.save_handler=memcache 如果需要扩展自定义的session管理方法 ,仅仅需要继承CHttpSession, 重写openSession,readSession,writeSession, destroySession,gcSession 这五个方法即可
3、数据库缓存配置:
//main.php配置文件中 数据库缓存配置,具体参照CDbConnection
'schemaCachingDuration' => 3600, //缓存时间
'schemaCachingExclude' => array(), //不需要缓存的表名数组
'schemaCacheID' => 'cache', //使用的缓存组件
此外,CDbConnection中还有关于数据库查询的缓存配置,不过这里只是一个全局的配置
//查询缓存配置
'queryCachingDuration' => 3600,//缓存时间
'queryCachingDependency' => null, //缓存依赖
'queryCachingCount' => 2, //第一次使用这条sql语句后同样的多少条sql语句需要缓存
'queryCacheID' => 'cache', //使用的缓存组件
需要配置数据库查询缓存,可以使用使用以下方法:
一、CDbConnection中的cache方法
public CDbConnection cache(integer $duration, CCacheDependency $dependency=NULL,integer $queryCount=1) $duration integer 查询结果保持在缓存中有效的秒数。如果它是0,缓存将被禁用。
$dependency CCacheDependency 当查询结果保存到缓存时,使用的依赖。
$queryCount integer 在调用此方法后,需要缓存的SQL查询的数目。默认为 1,意味着下一条SQL查询将被缓存。
{return} CDbConnection 返回连接实例本身。
public function cache($duration, $dependency=null, $queryCount=1)
{
$this->queryCachingDuration=$duration;
$this->queryCachingDependency=$dependency;
$this->queryCachingCount=$queryCount;
return $this;
}
例子:
$sql = 'SELECT * FROM tbl_post LIMIT 20';
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
二、CActiveRecord中的cache方法:
public CActiveRecord cache(integer $duration, CCacheDependency $dependency=NULL, integer $queryCount=1)
$duration integer 查询结果可能保持在缓存中有效的秒数。如果这是0,缓存将被禁用。
$dependency CCacheDependency 保存查询结果到缓存时使用的依赖关系。
$queryCount integer 在调用此方法后,需要缓存的SQL查询的数目。 默认值为 1,这意味着下一个SQL查询将被缓存。
{return} CActiveRecord AR实例本身。
public function cache($duration, $dependency=null, $queryCount=1)
{//实际上他调用的是
CDbConnection中的cache方法
$this->getDbConnection()->cache($duration, $dependency, $queryCount);return $this;
}
例子:
$dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
$posts = Post::model()->cache(1000, $dependency)->findAll();
关于$queryCount:
启用查询缓存
要使用查询缓存,首先要确保CDbConnection::queryCacheID指向一个可用的缓存组件ID(默认是cache)。
用DAO查询缓存
要使用查询缓存,我们在数据库查询的时候调用CDbConnection::cache()这个方法。例如:
- $sql = 'SELECT * FROM tbl_post LIMIT 20';
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl_post');
- $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
执行以上语句的时候,Yii会首先检查一下缓存是否包含一个该语句的查询结果。检查步骤是以下的三个条件:
●如果缓存包含了SQL语句中的入口索引
●如果入口还没过期(少于保存后的1000秒)
●如果依赖关系没有变化(update_time的最大值是跟查询结果保存到缓存时一致)
如果以上3个条件都满足了,缓存的结果就会直接返回给请求。否则,SQL语句就会被传递到数据库系统去执行,得到的结果会保存到缓存,返回给请求。
用AR查询缓存
我们也可以用AR来查询缓存。我们用一个类似的方法,调用CActiveRecord::cache():
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');
- $posts = Post::model()->cache(1000, $dependency)->findAll();
- // relational AR query
- $posts = Post::model()->cache(1000, $dependency)->with('author')->findAll();
上面的cache()方法,实际上是CDbConnection::cache()的快捷方式。在内部,当执行AR的查询语句是,Yii会尝试我们之前讲述过的查询缓存。
缓存的多种查询
默认情况下,我们每次调用cache()(不管是CDbConnection 还是 CActiveRecord),都会标记下次要缓存的SQL,其他任何的SQL查询都不会被缓存,除非我们再次调用cache(),例如:
- $sql = 'SELECT * FROM tbl post LIMIT 20';
- $dependency = new CDbCacheDependency('SELECT MAX(update_time) FROM tbl post');
- $rows = Yii::app()->db->cache(1000, $dependency)->createCommand($sql)->queryAll();
- // query caching will NOT be used
- $rows = Yii::app()->db->createCommand($sql)->queryAll();
通过传递另一个参数$queryCount到cache()的方法中,我们可以强制多次查询缓存。下面的例子中,通过调用call(),我们指定这个换成必须用于接下来的2次
- // ...
- $rows = Yii::app()->db->cache(1000, $dependency, 2)->createCommand($sql)->queryAll();
- // query caching WILL be used
- $rows = Yii::app()->db->createCommand($sql)->queryAll();
如你所知,当我们执行关联AR查询时,可能是执行多句的SQL。例如,Post与Coment之间的关系是HAS_MANY,以下的SQL语句是数据库中真正执行的。
it first selects the posts limited by 20;
it then selects the comments for the previously selected posts.
- $posts = Post::model()->with('comments')->findAll(array(
- 'limit'=>20,
- ));
如果我们用下面的语句查询缓存,只有第一句会被缓存:
- $posts = Post::model()->cache(1000, $dependency)->with('comments')->findAll(array(
- 'limit'=>20,
- ));
如果要缓存两个,我们要提供额外的参数来指明接下来我们要缓存几句:
- $posts = Post::model()->cache(1000, $dependency, 2)->with('comments')->findAll(array(
- 'limit'=>20,
- ));
限制
如果查询结果中包含资源句柄,查询访问就不能用了。例如,我们在某些数据库系统中使用BLOB作为字段类型,查询结果会返回一个资源句柄给这个字段。
有些缓存器会有大小限制。例如mencache限制每个入口大小为1M,所以,当一个查询结果大于该大小时,会缓存失败。
二、缓存的运用
提示 : 因为所有这些缓存组件都从同一个基础类 CCache 扩展而来,不需要修改使用缓存的代码即可在不同的缓存组
件之间切换。
缓存可以在不同的级别使用。在最低级别,我们使用缓存来存储单个数据,比如一个变量,我们把它叫做 数据缓存 。
往上一级,我们缓存一个由视图脚本生成的页面片断。在最高级别,我们存储整个页面以便需要的时候直接从缓存读
取。
接下来我们将阐述如何在这些级别上使用缓存。
注意 : 按定义来讲 , 缓存是一个不稳定的存储媒介 , 它不保证缓存一定存在 —— 不管该缓存是否过期 。 所以 , 不要使用
缓存进行持久存储(比如,不要使用缓存来存储 SESSION 数据 ) 。
一、数据缓存
数据缓存也就是在缓存中存储一些 PHP 变量 , 过一会再取出来 。 缓存基础类 CCache 提供了两个最常用的方法 : set()
和 get() 。
要在缓存中存储变量 $value ,我们选择一个唯一 ID 并调用 set() 来存储它:
Yii::app()->cache->set($id, $value);
被缓存的数据会一直保留在缓存中 , 直到因一些缓存策略而被删除 ( 比如缓存空间满了 , 删除最旧的数据 ) 。 要改变这
一行为,我们还可以在调用 set() 时加一个过期参数,这样数据过一段时间就会自动从缓存中清除。
// 在缓存中保留该值最多 30 秒
Yii::app()->cache->set($id, $value, 30);
当我们稍后需要访问该变量时 ( 不管是不是同一 Web 请求 ) , 我们调用 get() ( 传入 ID ) 来从缓存中获取它 。 如果返
回值为 false ,说明该缓存不可用,需要我们重新生成它。
$value=Yii::app()->cache->get($id);
if($value===false)
{
// 因为在缓存中没找到,重新生成 $value
// 再缓存一下以备下次使用
// Yii::app()->cache->set($id,$value);
}
为一个要缓存的变量选择 ID 时,确保该 ID 在应用中是唯一的。不必保证 ID 在跨应用的情况下保证唯一,因为缓
存组件有足够的智能来区分不同应用的缓存 ID 。
要从缓存中删除一个缓存值 , 调用 delete() ; 要清空所有缓存 , 调用 flush() 。 调用 flush() 时要非常小心 , 因为它会把
其它应用的缓存也清空。
提示 : 因为 CCache 实现了 ArrayAccess 接口,可以像数组一样使用缓存组件。例如:
$cache=Yii::app()->cache;
$cache['var1']=$value1; // 相当于 : $cache->set('var1',$value1);
$value2=$cache['var2']; // 相当于 : $value2=$cache->get('var2');
缓存依赖
除了过期设置,缓存数据还会因某些依赖条件发生改变而失效。如果我们缓存了某文件的内容,而该文件后来又被更
新了,我们应该让缓存中的拷贝失效,从文件中读取最新内容(而不是从缓存 ) 。
我们把一个依赖关系表现为一个 CCacheDependency 或它的子类的实例,调用 set() 的时候把依赖实例和要缓存的数
据一起传入。
// 缓存将在 30 秒后过期
// 也可能因依赖的文件有更新而更快失效
Yii::app()->cache->set($id, $value, 30, new CFileCacheDependency(‘FileName’));
如果我们现在调用 get() 从缓存中获取 $value ,缓存组件将检查依赖条件。如果有变,我们会得到 false 值 —— 数据
需要重新生成。
下面是可用的缓存依赖的简要说明:
CFileCacheDependency: 该依赖因文件的最近修改时间发生改变而改变。
CDirectoryCacheDependency: 该依赖因目录(或其子目录)下的任何文件发生改变而改变。
CDbCacheDependency: 该依赖因指定的 SQL 语句的查询结果发生改变而改变。
CGlobalStateCacheDependency: 该依赖因指定的全局状态值发生改变而改变。全局状态是应用中跨请求、跨 SESSION
的持久变量,它由 CApplication::setGlobalState() 来定义。
CChainedCacheDependency: 该依赖因依赖链中的任何一环发生改变而改变。
二、片段缓存 (Fragment Caching)
片段缓存指缓存网页某片段。例如,如果一个页面在表中显示每年的销售摘要,我们可以存储此表在缓存中,减少每
次请求需要重新产生的时间。
要使用片段缓存,在控制器视图脚本中调用 CController::beginCache() 和 CController::endCache() 。这两种方法开始和
结束包括的页面内容将被缓存。类似 data caching ,我们需要一个编号,识别被缓存的片段。
… 别的 HTML 内容 …
<?php if($this->beginCache($id)) { ?>
... 被缓存的内容 ...
<?php $this->endCache(); } ?>
... 别的 HTML 内容 ...
在上面的,如果 beginCache() 返回 false ,缓存的内容将此地方自动插入 ; 否则,在 if 语句内的内容将被执行并 在
endCache() 触发时缓存。
例如:
<?php if($this->beginCache('tagCloud', array('duration'=>60))) { ?>
<?php $this->widget('TagCloud'); ?>
<?php $this->endCache(); } ?>
<?php if($this->beginCache('RecentPosts', array('duration'=>60))) { ?>
<?php $this->widget('RecentPosts'); ?>
<?php $this->endCache(); } ?>
<?php if($this->beginCache('RecentComments', array('duration'=>60))) { ?>
<?php $this->widget('RecentComments'); ?>
<?php $this->endCache(); } ?
1. 缓存选项 (Caching Options)
当调用 beginCache() ,可以提供一个数组由缓存选项组成的作为第二个参数,以自定义片段缓存。事实上为了方便,
beginCache() 和 endCache() 方法是 COutputCache widget 的包装。因此 COutputCache 的所有属性都可以在缓存选项中
初始化。
2. 有效期( Duration )
也许是最常见的选项是 duration , 指定了内容在缓存中多久有效 。 和 CCache::set() 过期参数有点类似 。 下面的代码缓存
内容片段最多一小时:
… 其他 HTML 内容 …
<?php if($this->beginCache($id, array('duration'=>3600))) { ?>
... 被缓存的内容 ...
<?php $this->endCache(); } ?>
… 其他 HTML 内容 …
如果我们不设定期限,它将默认为 60 ,这意味着 60 秒后缓存内容将无效。
3. 依赖 (Dependency)
像 data caching ,内容片段被缓存也可以有依赖。例如,文章的内容被显示取决于文章是否被修改。
要指定一个依赖 , 我们建立了 dependency 选项 , 可以是一个实现 ICacheDependency 的对象或可用于生成依赖对象的配
置数组。下面的代码指定片段内容取决 lastModified 列的值是否变化:
… 其他 HTML 内容 …
<?php if($this->beginCache($id, array('dependency'=>array(
'class'=>'system.caching.dependencies.CDbCacheDependency',
'sql'=>'SELECT MAX(lastModified) FROM Post')))) { ?>
... 被缓存的内容 ...
<?php $this->endCache(); } ?>
... 其他 HTML 内容 ...
4. 变化 (Variation)
缓存的内容可根据一些参数变化。例如,每个人的档案都不一样。缓存的档案内容将根据每个人 ID 变化。这意味着 ,
当调用 beginCache() 时将用不同的 ID 。
COutputCache 内置了这一特征,程序员不需要编写根据 ID 变动内容的模式。以下是摘要。
varyByRoute: 设置此选项为 true ,缓存的内容将根据 route 变化。因此,每个控制器和行动的组合将有一个单独的缓
存内容。
varyBySession: 设置此选项为 true , 缓存的内容将根据 session ID 变化 。 因此 , 每个用户会话可能会看到由缓存提供的
不同内容。
varyByParam: 设置此选项的数组里的名字 , 缓存的内容将根据 GET 参数的值变动 。 例如 , 如果一个页面显示文章的内
容根据 id 的 GET 参数,我们可以指定 varyByParam 为 array(‘id’) ,以使我们能够缓存每篇文章内容。如果没有这样的
变化,我们只能能够缓存某一文章。
这里有问题要讨论:
我可以正确使用以下数据调用
if($this->beginCache('good_list_manager',array('duration'=>3600,'varyByParam'=>array('id')))){?>
{
// ... display the content to be cached here
$this->endCache();
}
但是以下代码不能使用,因为其参数为‘Goods[name]’YII无法正确识别!
if($this->beginCache('good_list_manager',array('duration'=>3600,'varyByParam'=>array("Goods[name]")))
{
// ... display the content to be cached here
$this->endCache();
}
解决方法:自己建立根据 ID 变动内容的模式。
if($this->beginCache('good_list_manager_'.$_GET[Goods][name].'_'.$_GET[Goods][bn].'_'.$_GET[Goods][cat_id].'_'.$_GET[Goods][brand_id].'_'.$_GET[Goods][marketable].'_'.$_GET[Goods][cps_status].'_'.$_GET[Goods][is_check],array('duration'=>3600))){?>
{
// ... display the content to be cached here
$this->endCache();
}
5. 请求类型(Request Types)
有时候,我们希望片段缓存只对某些类型的请求启用。例如,对于某张网页上显示表单,我们只想要缓存 initially
requested 表单 ( 通过 GET 请求 ) 。任何随后显示(通过 POST 请求)的表单将不被缓存,因为表单可能包含用户输入。
要做到这一点,我们可以指定 requestTypes 选项:
... 其他 HTML 内容 ...
<?php if($this->beginCache($id, array('requestTypes'=>array('GET')))) { ?>
... 被缓存的内容 ...
<?php $this->endCache(); } ?>
... 其他 HTML 内容 ...
6. 嵌套缓存 (Nested Caching)
片段缓存可以嵌套。就是说一个缓存片段附在一个更大的片段缓存里。例如,意见缓存在内部片段缓存,而且它们一
起在外部缓存中在文章内容里缓存。
... 其他 HTML 内容 ...
<?php if($this->beginCache($id1)) { ?>
... 外部被缓存内容 ...
<?php if($this->beginCache($id2)) { ?>
... 内部被缓存内容 ...
<?php $this->endCache(); } ?>
... 外部被缓存内容 ...
<?php $this->endCache(); } ?>
... 其他 HTML 内容 ...
嵌套缓存可以设定不同的缓存选项。例如, 在上面的例子中内部缓存和外部缓存可以设置时间长短不同的持续值 。 当
数据存储在外部缓存无效 , 内部缓存仍然可以提供有效的内部片段 。 然而 , 反之就不行了 。 如果外部缓存包含有效的
数据, 它会永远保持缓存副本,即使内容中的内部缓存已经过期。
三、页面缓存
页面缓存指的是缓存整个页面的内容。页面缓存可以发生在不同的地方。例如,通过选择适当的页面头,客户端的浏
览器可能会缓存网页浏览有限时间。 Web 应用程序本身也可以在缓存中存储网页内容。 在本节中,我们侧重于后一
种办法。
页面缓存可以被看作是 片段缓存 (/doc/guide/caching.fragment) 一个特殊情况 。 由于网页内容是往往通过应用布局来生
成,如果我们只是简单的在布局中调用 beginCache() 和 endCache() ,将无法正常工作。这是因为布局 在
CController::render() 方法里的加载是在页面内容产生之后。
缓存整个页面,我们应该跳过产生网页内容的动作执行。我们可以使用 COutputCache 作为动作 过滤器
( /doc/guide/basics.controller#filter )来完成这一任务。下面的代码演示如何配置缓存过滤器:
public function filters()
{
return array(
array(
'system.web.widgets.COutputCache',
'duration'=>100,
'varyByParam'=>array('id'),
),
);
}
上述过滤器配置会使过滤器适用于控制器中的所有行动。我们可能会限制它在一个或几个行动通过使用插件操作器。
更多的细节中可以看过滤器( /doc/guide/basics.controller#filter ) 。
提示 : 我们可以使用 COutputCache 作为一个过滤器 , 因为它从 CFilterWidget 继承过来 , 这意味着它是一个工具 (widget)
和一个过滤器。事实上, widge 的工作方式和过滤器非常相似:工具 widget ( 过滤器 filter) 是在 action 动作里的内容执
行前执行,在执行后结束。
四、动态内容(Dynamic Content)
当使用 fragment caching 或 page caching , 我们常常遇到的这样的情况整个部分的输出除了个别地方都是静态的 。 例如 ,
帮助页可能会显示静态的帮助信息,而用户名称显示的是当前用户的。
解决这个问题,我们可以根据用户名匹配缓存内容,但是这将是我们宝贵空间一个巨大的浪费,因为缓存除了用户名
其他大部分内容是相同的。我们还可以把网页切成几个片段并分别缓存,但这种情况会使页面和代码变得非常复杂。
更好的方法是使用由 CController 提供的动态内容 dynamic content 功能 。
动态内容是指片段输出即使是在片段缓存包括的内容中也不会被缓存。即使是包括的内容是从缓存中取出,为了使动
态内容在所有时间是动态的,每次都得重新生成。出于这个原因,我们要求动态内容通过一些方法或函数生成。
调用 CController::renderDynamic() 在你想的地方插入动态内容。
... 别的 HTML 内容 ...
<?php if($this->beginCache($id)) { ?>
... 被缓存的片段内容 ...
<?php $this->renderDynamic($callback); ?>
... 被缓存的片段内容 ...
<?php $this->endCache(); } ?>
... 别的 HTML 内容 ...
renderDynamic复杂调用形式:
$this->renderDynamic(‘widget’,'application.extensions.uinfo’,array(‘uid’=>’hahahah!’), true);
就相当于
$this->widget(‘application.extensions.uinfo’,array(‘uid’=>’hahahah!’), true);
在上面的, $callback 指的是有效的 PHP 回调。它可以是指向当前控制器类的方法或者全局函数的字符串名。它也可
以是一个数组名指向一个类的方法。其他任何的参数,将传递到 renderDynamic() 方法中。回调将返回动态内容而不是
仅仅显示它。
三、缓存注意事项:
请小心使用缓存,因为它增加了应用程序的性能,但降低了交互性。 (Please be careful to use the cache, because it increases the performance of the application, but decreases the interactivity.)
四、关于cacheKey的一些命名:
(1) CCacheHttpSession 中cacheKey的命名:
/**
* Prefix to the keys for storing cached data
*/
const CACHE_KEY_PREFIX='Yii.CCacheHttpSession.';
/**
* Generates a unique key used for storing session data in cache.
* @param string $id session variable name
* @return string a safe cache key associated with the session variable name
*/
protected function calculateKey($id)
{
return self::CACHE_KEY_PREFIX.$id;
}
使用:
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
* @return string the session data
*/
public function readSession($id)
{
$data=$this->_cache->get($this->calculateKey($id));
return $data===false?'':$data;
}
(2) COutputCache 中cacheKey的命名:
const CACHE_KEY_PREFIX='Yii.COutputCache.';
/**
* Caclulates the base cache key.
* The calculated key will be further variated in {@link getCacheKey}.
* Derived classes may override this method if more variations are needed.
* @return string basic cache key without variations
*/
protected function getBaseCacheKey()
{
return self::CACHE_KEY_PREFIX.$this->getId().'.';
}
/**
* Calculates the cache key.
* The key is calculated based on {@link getBaseCacheKey} and other factors, including
* {@link varyByRoute}, {@link varyByParam} and {@link varyBySession}.
* @return string cache key
*/
protected function getCacheKey()
{
if($this->_key!==null)
return $this->_key;
else
{
$key=$this->getBaseCacheKey().'.';
if($this->varyByRoute)
{
$controller=$this->getController();
$key.=$controller->getUniqueId().'/';
if(($action=$controller->getAction())!==null)
$key.=$action->getId();
}
$key.='.';
if($this->varyBySession)
$key.=Yii::app()->getSession()->getSessionID();
$key.='.';
if(is_array($this->varyByParam) && isset($this->varyByParam[0]))
{
$params=array();
foreach($this->varyByParam as $name)
{
if(isset($_GET[$name]))
$params[$name]=$_GET[$name];
else
$params[$name]='';
}
$key.=serialize($params);
}
$key.='.';
if($this->varyByExpression!==null)
$key.=$this->evaluateExpression($this->varyByExpression);
$key.='.';
return $this->_key=$key;
}
}
(3) CStatePersister 中cacheKey的命名:
//从持久存储加载状态数据。
public function load()
{
$stateFile=$this->stateFile;
if($this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null)
{
$cacheKey='Yii.CStatePersister.'.$stateFile; //路径名加文件名
if(($value=$cache->get($cacheKey))!==false)
return unserialize($value);
else if(($content=@file_get_contents($stateFile))!==false)
{
$cache->set($cacheKey,$content,0,new CFileCacheDependency($stateFile));
return unserialize($content);
}
else
return null;
}
else if(($content=@file_get_contents($stateFile))!==false)
return unserialize($content);
else
return null;
}
//保存应用程序状态到持久存储。
public function save($state)
{
file_put_contents($this->stateFile,serialize($state),LOCK_EX);
}
五、关于各个组件中用到的cache部分:
(1)CUrlManager 中
const CACHE_KEY='Yii.CUrlManager.rules';
public $cacheID='cache';
/**
* Processes the URL rules.
*/
protected function processRules()
{
if(empty($this->rules) || $this->getUrlFormat()===self::GET_FORMAT)
return;
if($this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null)
{
$hash=md5(serialize($this->rules)); //这里写的很巧妙,用md5生成一个固定的字符串来保存rules,相当于一个缓存依赖中的 generateDependentData() 方法,通过它来判断是规则变了,用md5比较起来好比较
//如果缓存存在,并且缓存依赖检查通过($data[1]===$hash),则返回缓存中存储的rules($data[0])
if(($data=$cache->get(self::CACHE_KEY))!==false && isset($data[1]) && $data[1]===$hash)
{
$this->_rules=$data[0];
return;
}
}
foreach($this->rules as $pattern=>$route)
$this->_rules[]=$this->createUrlRule($route,$pattern);
if(isset($cache))
$cache->set(self::CACHE_KEY,array($this->_rules,$hash)); //加$hash是为了下次访问时判断是否该rules变化过没,相当于CCacheDependency中的 evaluateDependency()来判断是否变化
}
五、关于CDummyCache :
CDummyCache是一个占位缓存组件。 他里面的方法和cache中的方法一样,不过是把设置缓存的代码给取消了,只返回操作成功后的结果,无实际操作代码。
CDummyCache不缓存任何数据。它使得我们总是可以配置一个缓存应用组件而不必检查Yii::app()->cache是否为null。将CDummyCache替换为其他缓存组件,可以快速的在无缓存模式和缓存模式之间切换。
测试阶段可以用他关闭缓存,设置如下:
//缓存配置
'cache' => array(
'class' => 'CDummyCache',
),
六、关于COutputCache:
想弄清CoutputCache,不得不知CController中的相关方法:
CController用到的关于cache的内容有:
private $_cachingStack; //存储着关于各个CoutputCache的一个栈
/**
* Renders dynamic content returned by the specified callback.
* This method is used together with {@link COutputCache}. Dynamic contents
* will always show as their latest state even if the content surrounding them is being cached.
* This is especially useful when caching pages that are mostly static but contain some small
* dynamic regions, such as username or current time.
* We can use this method to render these dynamic regions to ensure they are always up-to-date.
*(输出一个由特殊回调函数返回的动态内容,这个方法被CoutputCache调用,动态内容始终保持最新的数据,尽管他所在的内容 被缓存了。)
* The first parameter to this method should be a valid PHP callback, while the rest parameters
* will be passed to the callback. (这个函数的第一个参数是一个回调函数,其他的参数会作为参数传递给这个回调函数)
*
* Note, the callback and its parameter values will be serialized and saved in cache.
* Make sure they are serializable. (注意:回调函数和传递给回调函数的参数将要被序列化存储到cache中,所以,
请确保他们是可序列化的)
*
* @param callback $callback a PHP callback which returns the needed dynamic content.
* When the callback is specified as a string, it will be first assumed to be a method of
the current
* controller class. If the method does not exist, it is assumed to be a global PHP function.
(如果这个回调函数是字符串,他会假设这个函数是当前控制器里的一个方法,如果这个方法不存在,就假设这个这个方法是个全局的
函数)
* Note, the callback should return the dynamic content instead of echoing it.
(这个回调函数应该返回动态的内容,而不是输出他)
*/
public function renderDynamic($callback)
{
$n=count($this->_dynamicOutput);
echo "<###dynamic-$n###>";
$params=func_get_args();
array_shift($params);
$this->renderDynamicInternal($callback,$params);
}
/**
* This method is internally used. (通过回调函数调用把处理结果放到_dynamicOutput数组中)
* @param callback $callback a PHP callback which returns the needed dynamic content.
* @param array $params parameters passed to the PHP callback
* @see renderDynamic
*/
public function renderDynamicInternal($callback,$params)
{
$this->recordCachingAction('','renderDynamicInternal',array($callback,$params));
if(is_string($callback) && method_exists($this,$callback))
$callback=array($this,$callback);
$this->_dynamicOutput[]=call_user_func_array($callback,$params);
}
/**
* Records a method call when an output cache is in effect.
* When the content is served from the output cache, the recorded
* method will be re-invoked.
* @param string $context a property name of the controller. It refers to an object
* whose method is being called. If empty it means the controller itself.(需要记录的方法
所在的对象,如果为空,则默认为当前的控制器这个对象)
* @param string $method the method name (需要记录的方法)
* @param array $params parameters passed to the method (传递给这个方法的参数)
* @see COutputCache
*/
public function recordCachingAction($context,$method,$params)
{
if($this->_cachingStack) // record only when there is an active output cache
{
foreach($this->_cachingStack as $cache)
$cache->recordAction($context,$method,$params);
}
}
/**
* Postprocesses the dynamic output. (处理$output中插入的动态内容,处理过的内容是占位符被替换了真是的内容)
* This method is internally used. Do not call this method directly.
* @param string $output output to be processed
* @return string the processed output
*/
public function processDynamicOutput($output)
{
if($this->_dynamicOutput)
{
//调用replaceDynamicOutput来处理每个匹配
$output=preg_replace_callback('/<###dynamic-(\d+)###>/',array($this,'replaceDynamicOutput'),$output);
}
return $output;
}
/**
* Replaces the dynamic content placeholders with actual content.
* This is a callback function used internally. (替换匹配到的每个动态内容占位符,由processDynamicOutput
调用)
* @param array $matches matches
* @return string the replacement
* @see processOutput
*/
protected function replaceDynamicOutput($matches)
{
$content=$matches[0]; //所有的内容
if(isset($this->_dynamicOutput[$matches[1]]))
{
$content=$this->_dynamicOutput[$matches[1]];
$this->_dynamicOutput[$matches[1]]=null;
}
return $content;
}
/**
* @param boolean $createIfNull whether to create a stack if it does not exist yet. Defaults to
true. (创建一个存储COutputCache对象的栈)
* @return CStack stack of {@link COutputCache} objects
*/
public function getCachingStack($createIfNull=true)
{
if(!$this->_cachingStack)
$this->_cachingStack=new CStack;
return $this->_cachingStack;
}
/**
* Returns whether the caching stack is empty. (判断存储CoutputCache对象的栈是否为空)
* @return boolean whether the caching stack is empty. If not empty, it means currently there are
* some output cache in effect. Note, the return result of this method may change when it is
* called in different output regions, depending on the partition of output caches.
*/
public function isCachingStackEmpty()
{
return $this->_cachingStack===null || !$this->_cachingStack->getCount();
}
/**
* Postprocesses the output generated by {@link render()}.
* This method is invoked at the end of {@link render()} and {@link renderText()}.
* If there are registered client scripts, this method will insert them into the output
* at appropriate places. If there are dynamic contents, they will also be inserted.
* This method may also save the persistent page states in hidden fields of
* stateful forms in the page.
* @param string $output the output generated by the current action
* @return string the output that has been processed.
*/
public function processOutput($output)
{
Yii::app()->getClientScript()->render($output); //插入静态的js,css文件等
// if using page caching, we should delay dynamic output replacement
if($this->_dynamicOutput!==null && $this->isCachingStackEmpty())
{
$output=$this->processDynamicOutput($output);
$this->_dynamicOutput=null;
}
if($this->_pageStates===null)
$this->_pageStates=$this->loadPageStates();
if(!empty($this->_pageStates))
$this->savePageStates($this->_pageStates,$output);
return $output;
}