起因

我们们知道,Yii内部的一些有用的类都设有默认的参数,比如 [[yii\data\Pagination]] 的默认每页个数是 20 个.

对于项目中的列表来说,有点多. 那么怎么才能一劳永逸的配置让所有的地方都使用我们的设定值呢?

这时候,我们可以使用 [[Yii::$contianer]] 的来实现:

1
2
3
4
//这里我们设置默认个数为10, 将此置于 bootstrap.php 中
\Yii::$container->set('yii\data\Pagination', [
'pageSize' => 10,
]);

这样, 我们在使用诸如 ActiveDataProvider 等方法时,默认每页个数就为 10 了。

思考

理论上,通过 Yii::createObject 来实例化的对用都会起作用。

所以,说用通过 Yii::createObject 创建的实例都会使用我们通过以上方法设定的默认值。因为 ActiveDataProvider 等中的 pagination 在内部是通过 Yii::createObject 创建的,所以设置是生效的。

因此,考虑一下两种实现方式:

1
2
3
4
//1. pageSize 还是默认的20
$paging = new \yii\data\Pagination;
//2. pageSize 就是设置的 10 了
$paging = Yii::createObject('yii\data\Pagination');

所以Yii提倡使用 Yii::createObject 来实例化对象,因为它能够自动解决依赖和默认值配置等好处。

Yii::$container 是什么

1
Yii::$container = new yii\di\Container();

所以 Yii::$container 就是整个生命周期的一个依赖容器。

考虑开始的时候的代码,我们可以理解: 通过 Yii::$container->set() 其实就是向这个容器注入了一个类和其配置. 当调用 Yii::createObject 的时候会去这个容器里面找,如果存在则根据 set 的配置进行实例化。

推荐

关于依赖注入和依赖注入容器可以查看博文:http://www.digpage.com/di.html

项目上线运行中,由于这样那样奇葩的浏览器的存在,导致我们的js代码存在一定的兼容性问题。那么可否收集到这些问题呢?

答案是肯定的。我们可以基于 window.onerror 事件来收集并上报前段js代码在实际运行中产生的错误,以帮助我们排查解决。

在W3C规范里,window.onerror是html5新定义的事件,但实际上,window.onerror从IE6开始就被支持了,而chrome、firefox、safari、opera,目前也都已经支持该事件。

1
2
3
4
5
6
7
8
9
10
//收集js报错信息并上报
window.onerror = function(msg, url, line, column, error) {
var _e = encodeURIComponent,
content = '/site/jslog?message=' + _e(msg) +
'&url=' + _e(url || window.location.href) + '&line=' + _e(line) +
(error && error.stack ? '&stack=' + _e(error.stack) : '') +
(column ? '&column=' + _e(column) : '') +
'&ua=' + window.navigator.userAgent.replace(/[\:,;]/g,"|");
(new Image()).src = content;
}

服务端:

1
2
3
4
5
6
7
8
9
10
function actionJslog()
{
$response = Yii::$app->getResponse();
//这是必须的,我们要将header设置为image
$response->format = Response::FORMAT_RAW;
//write the log data
$headers = $response->getHeaders();
$headers->set('Content-Type', 'image/png');
return $response;
}

这段代码需要放在head中并且位于所有script之前!

这样当客户端发生js报错时就会上报到服务器端,在服务器端我只需要将提交过来的信息进行保存就可以了。这样我们就可以根据收集到的信息来解决问题。

这里有几个点需要注意,一个是日志收集接口暴露在外,最好增加安全措施,比如设置token。

另外,脚本部署的时机、位置,以及日志过滤等条件,也需要根据具体情况进行设定。

教训:

在代码中我们经常使用 console.log 来打印代码,但这存在问题的,某些浏览器是不支持该方法的!

这就会产生错误,使代码无法正常运行!有两张方法可以解决:

  1. 利用工具来消除所有的打印调试代码

  2. 提供兼容代码「置于head中所有script之前」

1
2
3
4
5
window.console = window.console || (function() {
var _c = {};
_c.log = _c.warning = _c.error = _c.info .. = function() {};
return _c;
})();

开发中遇到使用 ActiveRecord::update 来更新数据,那么执行 update 的时候都做了什么:

  • 检查属性的值是否发生改变,只有属性值改变的属性才保存

  • 存在更新但不会对数据行产生影响的情况,此情况下返回受影响的行数为0

根据以上,我们在使用的时候需要注意的有:

1
2
3
4
5
6
7
8
9
10
11
$user = User::findOne();

$user->status = 1; //假设原值也为1

$user->updated_at = now();

if ($user->update() === false) { //注意这里必须要进行严格的判断「update执行返回的是受影响的行数」
//update faild
} else {
//update success
}

顾名思义,从命名上来看,给人的感觉是进行布尔类型判断的。

但其实我们可以用它来做一些更有意思的比较。

比较常规的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function actionSearch()
{
$model = new DynamicModel(['status']);

$model->addRule('status', 'boolean');
$model->status = 1;
$model->validate(); // true, status 设置为 0 也会验证为true

$model->addRule('status', 'boolean', ['strict' => true]);
$model->status = 1;
$model->validate(); // false
$model->status = '1';
$model->validate(); // true
}

这里列举了两个验证场景:1是非严格比较, 2是严格比较。

在 BooleanValidator 中有两个属性分别代表比较的值,其默认为 trueValue = '1'falseValue = '0',

在验证的时候 BooleanValidator 分别对对相应的属性值进行比较,当有一个「trueValue或falseValue」分别在严格比较和非严格比较的情况下和属性值相等时则验证通过。

由此,我们可以通过设置 trueValue 和 falseValue 来进行更多的比较,例如:

1
2
3
4
5
6
7
8
9
10
//此场景我们需要验证用户的输入为 yes 或者 no

$model = new DynamicModel(['action']);
$model->load(Yii::$app->getRequest()->post(), '');
$model->addRule('status', 'boolean', ['strict' => true, 'trueValue' => 'yes', 'falseValue' => 'no']);
if ($model->validate()) {
//validate pass
} else {
//validate error
}

什么是DynamicModel

DynamicModel是用来进行临时的数据验证,有些情况,我们不希望为一次数据提交建立专门的Model来处理「例如:操作只是进行更改商品的状态」.

如何来用

DynamicModel::validateData

DynamicModel提供了静态方法 validateData 来辅助我们实现动态验证,考虑一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function actionUpdateStatus($id, $status)
{
$model = DynamicModel::validateData(compact('id', 'status'), [
[['id', 'status'], 'required'],
[['id', 'status'], 'integer'],
['status', 'in', 'range' => [1, 2]],
...
]);

if ($model->validate()) {
//validation success
} else {
//validation error
}
}

这里的 validateData 方法接受两个参数:model的属性值和对应的验证方法,它返回的是一个 DynamicModel的实例,拥有赋予的属性和对应的值,当然还有定义的验证方法。

面向对象的方法

调用 validateData 方法,并传入属性值和验证方法会为我们自动生成一个 DynamicModel 的实力,当然我们也可以:

1
2
3
4
5
6
7
8
9
10
$model = new DynamicModel(compact('id', 'status'));
$model->addRule(['id', 'status'], 'required')
->addRule(['id', 'status'], 'integer')
->addRule('status', 'in', ['range' => [1, 2]]);
...

if ($model->validate()) {
//validate success
}
...

更多

通过以上两个方法得到动态model后,我们可以通过 $model->id 的方式来获取对应的值。

当然也可以调用 $model->attributes() 来获取所有属性.

当希望为创建好的model新增属性时,我们可以:

1
2
$model->defineAttribute('user_id', 1111);
$model->defineAttribute('name');

需要删除某一属性时:

1
$model->undefineAttribute('name');

贤心的最懂你的、跨终端的web弹层组件layer由于没有发布到bower,所以为了在Yii2的项目中方便使用,特发布了 light/yii2-layer

这样我们就可以愉快的依赖 layer 这块强大的插件了。

安装

1
composer require light/yii2-layer=*

使用

1
2
3
use light\assets\LayerAsset;

LayerAsset::register($this);

或者在移动端使用:

1
2
3
use light\assets\LayerMobileAsset;

LayerMobileAsset::register($this);

具体这款插件有多炫酷,还是自行到官方站去体验吧! http://sentsin.com/jquery/layer/

说明

这里没有封装使用插件,因为目前还是在js中主动调用 - - !

什么是JSONP

JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)

具体可以参看下玉伯大神的一篇文章聊聊 JSONP 的 P

怎么发送JSONP请求

这里我使用jQuery的 getJSON 方法:

1
2
3
$.getJSON('http://www.b.com/api/users?callback=?', function(data) {
console.log(data)
});

这里jQuery一切都为我们做好了,只需要加上参数 callback=?

Yii接受处理并返回

先上代码:

1
2
3
4
5
6
7
8
9
10
Yii::$app->getResponse()->format = Response::FORMAT_JSONP;
$user_count = (new Query)->from('users')->count();

return [
'data' => [
'errcode' => 0,
'data' => ['count' => $user_count],
],
'callback' => Yii::$app->getRequest()->get('callback'),
];

首先,我们需要在 action 中指定 Response 的格式为 FORMAT_JSONP.

当设置 Response 的格式为 JSONP 后,同 FORMAT 设置为JSON 我们需要返回 数组数据

不同点是这个数组需要有两个关键的Key,一个是data,另外一个是 callback。

  • data 的内容就是回调中接收到的实际数据
  • callback 是前端发送的参数中的方法名

我们来看下Yii的源代码:

位于 [[yii\web\JsonResponseFormatter]]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Formats response data in JSONP format.
* @param Response $response
*/
protected function formatJsonp($response)
{
$response->getHeaders()->set('Content-Type', 'application/javascript; charset=UTF-8');
if (is_array($response->data) && isset($response->data['data'], $response->data['callback'])) {
$response->content = sprintf('%s(%s);', $response->data['callback'], Json::encode($response->data['data']));
} elseif ($response->data !== null) {
$response->content = '';
Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__);
}
}

可以看到,Yii做的处理有:

  • 设置HEADER的Content-Type为 application/javascript
  • 判断Response::data 是否为数组且设置了 data 和 callback 两个参数
  • 组装JSONP返回

  1. In package directory
1
$ npm link

This createsa symlink to the current folder in npm’s global installation directory.

  1. Somewhere else, where you want to use the modules:
1
$npm  link <pkgname>

This will create a symlink in your project’s node_modules/ folder to the global installation.

在目录中查找包含文字的文件

1
$ grep -l -r findstring /dir

查找文件夹

1
$ find -type d -name .svn

查找目录下所有的php文件

1
$ find -name "*.php"

批量删除文件

1
$ find -type d -name .svn | xargs rm -rf

查看linux版本

1
$ cat /etc/redhat-release