中似乎还有许多会产生频繁访问相关表的元素。
当我在简单的页面上进行测试时,第一次查询的响应时间是16个查询,耗时140毫秒,而后续的查询则变为6个查询,耗时88毫秒。这是一个实际工作中的例子,我成功将相当复杂页面的响应时间从200至400毫秒稳定地降低到70毫秒左右。
在这里,请记住 Yii 使用查询或数据提供程序等方式,直到实际渲染到视图之前不会访问数据库。Yii 的 ActiveRecord 也是默认延迟加载的。
在某种将所有数据都从控制器传送到模板引擎中,并且最终在 MVC 框架中对数据库进行同样多次查询的情况下,即使视图中有片段缓存,也无法实现关键的数据库访问避免。
通过在业务逻辑中创建查询,并坚持 Yii 的风格,在真正显示之前不获取,就不会出现需要关注缓存是否存在并引入 if 条件到逻辑中的情况。
Pjax + 片段缓存 = 极速
然后将这两个元素结合在一起,就能够迅速地完成网页。
<?php
use yii\widgets\Pjax;
?>
<?php Pjax::begin([
'linkSelector' => '#posts-pjax-region .pagination a',
'options' => [
'id' => '#posts-pjax-region'
]
]); ?>
<?php if ($this->beginCache('post_list', [
'variations' => [
'page' => Yii::$app->request->get('page', 1)
],
'dependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(updated_at) FROM posts',
],
'duration' => 180, // sec
])): ?>
<div class="posts">
<?php foreach($postsQuery->all() as $post): ?>
<div class="post">
...
</div>
<?php endforeach; ?>
</div>
<div class="pagination">
...
</div>
<?php $this->endCache(); ?>
<?php endif; ?>
<?php Pjax::end(); ?>
我认为,如果是个还算不错的服务器,即使是 PHP,每秒钟也可以输出大约 50 到 80 个页面吧。(因为理论上只需要从缓存中返回 HTML)
即使没有使用用于单页面应用程序的技术,也没有做任何特殊处理,普通的应用程序开发者也可以通过普通的 Web 页面应用程序创建,然后只需添加行来实现这一点,而不会破坏它,这就是 Yii 的出色之处。
有各种各样的补充信息
Pjax 事件
从我粗略阅读源代码的感觉来看,Pjax 事件似乎有以下内容。
-
- pjax:click
-
- pjax:clicked
-
- pjax:beforeSend
-
- pjax:timeout
-
- pjax:complete
-
- pjax:end
-
- pjax:error
-
- pjax:beforeReplace
-
- pjax:success
-
- pjax:start
-
- pjax:send
- pjax:popstate
可以通过以下方式捕获jQuery事件。
<?php $this->registerJs(<<<JS
jQuery('#posts-pjax-region').on('pjax:success', function() {
console.log('pjax success');
});
JS
) ?>
如果使用这个,当页面变化时,可以将广告换成不同的,甚至可以进行一些小花招。
Pjax 表单提交
Pjax 不只可用于导航,还可用于表单提交。当您希望能够在无需页面跳转的情况下频繁输入数据时,Pjax 将非常有用。
<?php Pjax::begin([
'formSelector' => '#data-editor form',
'options' => [
'id' => '#data-pjax-region'
]
]); ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'name',
'value',
]
]) ?>
<?php Pjax::end(); ?>
<div class="form-inline" id="data-editor">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($editorModel, 'name') ?>
<?= $form->field($editorModel, 'value') ?>
<?= Html::submitButton('Add', ['class' => 'btn btn-primary']) ?>
<?php $form->end(); ?>
</div>
Pjax通过formSelector来拦截表单提交,然后使用Ajax进行POST请求,并使用响应来更新Pjax区域。这样就可以在同一页上不断添加数据。
如果保持这样的状态,表单不会被清空,所以可以使用上面的发送成功事件。
在接收方,如果URL是通过POST方法传递过来的,需要添加一个选项来允许添加。对于Pjax请求,不能像通常的重定向一样,而是应该返回更新后的HTML。这里有点不同寻常。
public function actionIndex()
{
$editorModel = new DataModel();
if (Yii::$app->request->isPost) {
if ($editorModel->load(Yii::$app->request->post()) &&
$editorModel->save()
) {
if (!Yii::$app->request->isPjax) { // Pjaxでない場合のみ
return $this->redirect(...);
}
} else {
$errorMessage = $editorModel->...; // なんかうまいことやる
}
}
$dataProvider = ...;
$this->render(...);
}
也许使用REST控制器会更好。
如果你想做到正确的验证,不推荐这种方式。如果写入失败,需要将错误响应放入Pjax区域内,无法在表单中反映。不要太过努力,因为客户端验证可以控制提交按钮,所以就做到这个程度吧。
各种缓存 (gè
在缓存中,不仅可以使用片段缓存,还可以选择将纯数据缓存或者缓存整个HTML文件。
另外,我做了各种尝试,但如果用户没有登录会话,最好优先考虑使用Last-Modified和ETag来利用浏览器缓存。