# 缓存
缓存通过存储数据(一旦几乎不检索)来加速您的应用程序,以备将来使用。 我们将向您展示:
如何使用缓存
如何更改缓存存储
如何正确无效缓存
Nette Framework提供了一个非常直观的API用于缓存操作。 毕竟,你不会期望什么,对吧? ;-)在我们继续第一个例子之前,我们需要考虑地方在哪里存储数据。 我们可以使用数据库,Memcached服务器,或最可用的存储 - 硬盘驱动器:
~~~
// the `temp` directory will be the storage
$storage = new Nette\Caching\Storages\FileStorage('temp');
~~~
Nette \ Caching \ Storages \ FileStorage存储是非常优化的性能,首先,它提供操作的完全原子性。 这意味着什么? 当我们使用缓存时,我们可以确保我们没有读取一个尚未完全写入的文件(由另一个线程),或者该文件被删除“在我们手中”。 因此使用缓存是完全安全的。 有关高速缓存存储部分的更多信息。
对于使用缓存的操作,我们使用Nette \ Caching \ Cache
~~~
use Nette\Caching\Cache;
$cache = new Cache($storage); // $storage from the previous example
~~~
让我们将'$ data'变量的内容保存在'$ key'键下:
~~~
$cache->save($key, $data);
~~~
这样,我们可以从缓存中读取:(如果缓存中没有这样的项,则返回NULL值)
~~~
$value = $cache->load($key);
if ($value === NULL) ...
~~~
方法load()有第二个参数callable $ fallback,当缓存中没有这样的项时,它被调用。 这个回调通过引用接收数组$ dependencies,您可以使用它来设置过期规则。
~~~
$value = $cache->load($key, function(& $dependencies) {
// some calculation
return 15;
});
~~~
我们可以通过保存NULL或调用remove()方法从缓存中删除项:
~~~
$cache->save($key, NULL);
// or
$cache->remove($key);
~~~
Web应用程序通常由多个独立部分组成,如果它们都在同一存储(例如同一目录)中缓存数据,迟早会出现名称冲突。 Nette Framework通过将整个存储分割成段来解决这个问题(在使用子目录的FileStorage案例中)。 应用程序的每个部分都使用它自己的具有唯一名称的段,因此不会发生冲突。 该节的名称可以作为第二个参数传递给Cache类构造函数。 (这些部分通常称为缓存命名空间。)
~~~
$cache = new Cache($storage, 'htmlOutput');
~~~
## 在模板中缓存
在模板中缓存很容易,只需用{cache} ... {/ cache}包装模板的一部分。 当源模板更改时,缓存自动失效,包括{cache}宏中包含的任何模板。 {cache}块可以嵌套,并且当嵌套块被无效(例如通过标签)时,父块也被无效。
可以定义缓存将要绑定的键(在这种情况下,$ id变量),并设置过期和标签为无效
~~~
{cache $id, expire => '20 minutes', tags => [tag1, tag2]}
...
{/cache}
~~~
所有参数都是选项,因此您不必指定到期,标签或键。
使用缓存也可以使用“if”条件 - 只有在满足条件时才会缓存内容:
~~~
{cache $id, if => !$form->isSubmitted()}
{$form}
{/cache}
~~~
如果我们只从模板中检索数据(按需原则或延迟),缓存将特别有效。
## 缓存功能结果
缓存函数或方法调用的结果可以使用call()方法实现:
~~~
$name = $cache->call('gethostbyaddr', $ip);
~~~
因此,gethostbyaddr($ ip)只会被调用一次,下一次只返回缓存中的值。 当然,对于不同的$ ip,不同的结果被缓存。
## 输出缓存
输出不仅可以缓存在模板中:
~~~
if ($block = $cache->start($key)) [
... printing some data ...
$block->end(); // save the output to the cache
}
~~~
如果输出已经存在于高速缓存中,start()方法将打印它并返回NULL。 否则,它开始缓冲输出并返回$ block对象,我们最终将数据保存到缓存中。
## 到期和无效
这里有两个问题在缓存中存储数据。 首先,存储空间可能已完全填满,您无法在其中保存更多数据。 并且可能发生的是,先前保存的数据中的一些将随时间变得无效。 因此,Nette Framework提供了一种机制,如何限制数据的有效性以及如何以受控的方式删除它们(使用框架的术语来“使它们无效”)。
使用save()方法的第三个参数保存数据时,设置数据有效性:
~~~
$cache->save($key, $data, [
Cache::EXPIRE => '20 minutes', // accepts also seconds or a timestamp.
]);
~~~
从代码本身很明显,我们保存了接下来20分钟的数据。 在这段时间之后,缓存将报告在“$ key”键下没有记录(即,将返回NULL)。 事实上,您可以使用任何时间值,这是PHP函数strToTime()或DateTime类的有效值。 如果我们想要在每次阅读时延长有效期,可以这样做:
~~~
$cache->save($key, $data, [
Cache::EXPIRE => '20 minutes',
Cache::SLIDING => TRUE,
]);
~~~
非常方便的是,当特定文件被改变或者几个文件之一时,让数据过期的能力。 这可以用于将由于将这些文件解析到缓存而产生的数据。 为了无故障功能,建议使用绝对路径。
~~~
$cache->save($key, $data, [
Cache::FILES => 'data.yaml', // an array of files can also be specified
]);
~~~
Cache :: FILES标准,当然可以使用Cache :: EXPIRE等与时间到期结合。
缓存也可以依赖于其他缓存的项目。 这可以在我们将整个HTML页面保存在缓存中和不同的键(其某些片段)时使用。 一旦零件更改,整个页面将失效。
~~~
$cache->save('page', $html, [
// will expire if frag1 or frag2 expires
Cache::ITEMS => ['frag1', 'frag2'],
]);
~~~
过期可以通过自己的回调来控制:
~~~
function controlExpiration($val)
{
return $val;
}
$cache->save($key, $value, [
Cache::CALLBACKS => [['controlExpiration', 1]],
]);
~~~
## 到期使用标签和优先级
所谓的标签是一个非常有用的无效工具。 我们可以为存储在缓存中的每个项目分配一个标签列表。 例如,假设我们有一个HTML页面,其中包含我们要缓存的文章和注释。 所以我们在保存到缓存时指定标签:
~~~
$cache->save($articleId, $html, [
Cache::TAGS => ["article/$articleId", "comments/$articleId"],
]);
~~~
现在,让我们转到管理。 这里我们有一个文章编辑的表单。 与将文章保存到数据库一起,我们调用clean()命令,它将通过标签删除缓存的项目:
~~~
$cache->clean([
Cache::TAGS => ["article/$articleId"],
]);
~~~
在添加新评论(或编辑它们)的地方,不要忘记使适当的标记无效:
~~~
$cache->clean([
Cache::TAGS => ["comments/$articleId"],
]);
~~~
我们已经实现了什么? HTML缓存将自动失效。 每当有人更改ID为10的文章时,它会强制文章/ 10标记无效,并且缓存中标记的HTML页面被清除。 当某人在文章下面插入新评论时,也会发生同样的情况。
与标签类似,您可以按优先级控制到期日期:
~~~
$cache->save($key, $value, [
Cache::PRIORITY => 50,
]);
// all cached items with priority less than or equal to 100 will be removed.
$cache->clean([
Cache::PRIORITY => 100,
]);
~~~
## 缓存存储
除了已经提到的FileStorage之外,Nette Framework还提供了MemcachedStorage,用于将数据存储到Memcached服务器,还提供MemoryStorage用于在请求期间将数据存储在内存中。
## 存储服务
当然,可以创建自己的存储。 唯一的要求是实现IStorage接口。
我们可以使用依赖注入,所以我们不必在任何地方创建$ storage对象。 Nette框架提供了一种实现IStorage接口的服务。 如果没有在配置中指定具体的实现,默认情况下使用FileStorage,它将数据保存到由bootstrap.php中的$ configurator-> setTempDirectory()指定的目录中。
~~~
use Nette;
class MyPresenter
{
/**
* @inject
* @var Nette\Caching\IStorage
*/
public $storage;
public function actionDefault()
{
$cache = new Cache($this->storage, 'htmlFront');
$cache->save($key, $data);
}
}
~~~
## 为测试目的禁用缓存
Nette \ Caching \ IStorage的特殊实现是一个DevNullStorage。 它不保存任何数据。 当我们想要消除缓存的影响时,这通常在测试时很有用。
在config.neon中配置存储:
~~~
services:
cacheStorage:
class: Nette\Caching\Storages\DevNullStorage
~~~
## 并发缓存
删除缓存是将新应用程序版本上传到服务器时的常见操作。 然而,在那一刻,服务器变得非常困难,因为它必须构建一个完整的新缓存。 检索一些数据可能相当困难,例如RobotLoader缓存构建。 此外,如果例如30个请求在短时间内来到,则资源消耗甚至更高。
解决方案是修改应用程序行为,使数据只由一个线程创建,而其他线程正在等待。 为此,请将该值指定为回调或使用匿名函数:
~~~
$result = $cache->save($key, function() {
return buildData(); // difficult operation
});
~~~
框架将确保函数的主体将只被一个线程一次调用,而其他线程将等待。 如果线程由于某种原因失败,就找另一个机会。
## 在整个应用程序中使用缓存
当在服务中使用缓存时,我们面临的决定是将缓存或IStorage对象传递到服务中。 当服务只需要自己的缓存,我们传递IStorage:
~~~
use Nette\Caching;
class MyService
{
/** @var Caching\Cache */
private $cache;
public function __construct(Caching\IStorage $storage)
{
$this->cache = new Caching\Cache($storage, 'my-service');
}
}
~~~
服务从DI容器获取存储:
~~~
services:
- MyService
~~~
另一种情况是,当我们需要更多的相同服务的实例,但是使用分离的缓存时:
~~~
use Nette\Caching;
class MyService
{
/** @var Caching\Cache */
private $cache;
public function __construct(Caching\Cache $cache)
{
$this->cache = $cache;
}
}
~~~
我们分别为每个服务创建一个缓存对象:
~~~
services:
one: MyService( Nette\Caching\Cache(namespace: 'one') )
two: MyService( Nette\Caching\Cache(namespace: 'two') )
~~~
- Nette简介
- 快速开始
- 入门
- 主页
- 显示文章详细页
- 文章评论
- 创建和编辑帖子
- 权限验证
- 程序员指南
- MVC应用程序和控制器
- URL路由
- Tracy - PHP调试器
- 调试器扩展
- 增强PHP语言
- HTTP请求和响应
- 数据库
- 数据库:ActiveRow
- 数据库和表
- Sessions
- 用户授权和权限
- 配置
- 依赖注入
- 获取依赖关系
- DI容器扩展
- 组件
- 字符串处理
- 数组处理
- HTML元素
- 使用URL
- 表单
- 验证器
- 模板
- AJAX & Snippets
- 发送电子邮件
- 图像操作
- 缓存
- 本土化
- Nette Tester - 单元测试
- 与Travis CI的持续集成
- 分页
- 自动加载
- 文件搜索:Finder
- 原子操作