yii2开发中19条推荐实践(阿北总结)
环境说明
服务器环境: CentOS
开发环境及IDE:mac & phpstorm
Yii:v2.0.15 基础版
接下来开说
开发前
先说说开发前的事情,磨刀不误砍柴工,将yii2配置到一个最易开发的状态。
Composer
这个是做yii2开发的基石,除非没有办法使用,否则请不要放弃,除了更容易的安装yii2及第三方扩展外,能使用Composer代表着你的服务器最少能运行起来php-cli,那么你就可以使用yii命令行,它会为你的开发带来无尽的遍历。
对于composer,有些关键词你要特别关注 install、update、require。
中文化
默认安装yii2时,程序的相关信息是英文的,第一步我们需要改成中文的,很简单。
// config/web.php'language'=>'zh-CN'复制代码
// config/web.php
'language'=>'zh-CN'
静态缓存问题
在用yii2的时候,我们习惯将静态文件(图片、css文件、js文件等)放到资源类中管理,但是可能存在浏览器缓存问题,在开发阶段可以通过配置来避免这个问题,尤其是开发移动端页面的时候特别有用。
// config/web.php
'assetManager'=>[
'appendTimestamp' => true
],
配置DB
虽然yii2对数据库,尤其是对mysql是很友好的,但是我们还是应该使用稳定高一点的版本,别说你的程序将来没有移动端,早早的选择一个支持emoji的数据库会避免我们下载第三方库去解决报错问题。
如果可以
mysql5.3.3 +
config/db.php 的charset=utf8mb4
配置debug
如果可能,请配置一个类似于xdebug的PHP扩展并且集成到你的IDE中,开发过程中难免遇到不好捕获的bug,这需要你在一个yii2生命周期内持续的观察某些变量的值及赋值路径,具体配置可以参考我之前的课程,用xdebug支持yii2调试之 - PhpStorm配置篇
当然,yii2自己的debug扩展也极其有用,尤其配置urlManager的时候。
开发中
本段为你介绍我在yii2开发中一些习惯和小技巧,希望对你有用。
单一职责
一个类和一个方法应该只有一个职责,比如下面的代码
function getFullName(){
$isAdmin = Administrator::find()->where(['user_id'=>$this->id])->one();
if($isAdmin && $this->xxx == 1){
return $this->first_name . " " . $this->last_name;
}
}
比如上面的情况我们最好是将对是否为管理员的判断单独提取出来,如下
function getFullName(){
if($this->isAdmin() && $this->xxx == 1){
return $this->first_name . " " . $this->last_name;
}
}
function isAdmin(){
return Administrator::find()->one();
}
每个方法是一个最小化的问题解决单元,相关知识可以看下《重构 - 既有代码的改善》这本书,北哥大约三年前读过,很多小技巧,受益颇多。
模型的重要性
很多yii2的初学者喜欢将大量逻辑写到控制器的动作(action)中,这是不对的,我们的重点应该在模型中,而控制器仅仅是做输入输出。
我们拿关联举个例子,下面的这段代码是不好的。
// 某个控制器
public function actionIndex(){
// 这里还有很多代码
....
// 获得三天前的某个会员的订单集合
$order = Order::find()->where(["user_id"=>$userId])->andWhere([">","created_at",strtotime(date("Y-m-d",time()))-86400*3])->all();
}
我们最好将这段逻辑放到会员模型中
// User模型
public function recent3DaysOrders(){
return Order::find()->where(["user_id"=>$this->id])->andWhere([">","created_at",strtotime(date("Y-m-d",time()))-86400*3])->all();
}
// 控制器中
public function actionIndex(){
$order = $user->recent3DaysOrders();
}
控制器的代码力求简单,只做基本的输入帅选以及输出渲染。
规则
对与错,不要随便就写。
// 某个控制器的action中
public function actionCreate(){
$model = new User();
if(Yii::$app->request->isPost){
$model->load(Yii::$app->request->post());
if($model->xxx == xxxx){
// todo
}
if($model->save()){
//
}
}
}
上面的代码再熟悉不过了,这是我们期望的样子,但是有的时候输入并不会这样老实,我们需要进行更多验证,请不要将验证直接写到action内,比如上面代码中的if判断。
**将验证的工作交给模型的rule和场景吧。**一切。
复用随时要想到(小挂件)
编码的原则是尽最大努力让代码复用,尤其是小挂件,它让视图层实现了复用,小挂件的使用非常简单
1、在@app下建立一个文件夹components
2、在components内建立一个挂件类(必须继承yii\base\Widget)
3、渲染一个小挂件的视图(如果需要,在components/views下)
4、使用它
没看明白?我给个例子。
// components/Top10.php
<?php
namespace app\components;
use yii\base\Widget;
class Top10 extends Widget {
public function init(){
parent::init();
}
public function run(){
parent::run();
return $this->render('top10');
}
}
写一个视图
// components/views/top10.php
<h1>Hello Top10</h1>
// 某个视图
<?= \app\components\Top10::widget();?>
当然挂件可以很复杂,比如我们使用的ActiveForm、GridView等。关于小挂件我之前也写了一篇文章,有兴趣的同学可以看看。 传送门
AR关联的循环要很小心
这个问题我之前也视频说过,就是惰性加载和即时加载的问题,比如下面的代码并不好
$customers = Customer::find()->limit(100)->all();
foreach ($customers as $customer) {
// SELECT * FROM `order` WHERE `customer_id` = ...
$orders = $customer->orders;
}
上面的代码执行了101次查询,如果数据更多那?对于上面的问题我们是这样解决的。
// SELECT * FROM `customer` LIMIT 100;
// SELECT * FROM `orders` WHERE `customer_id` IN (...)
$customers = Customer::find()
->with('orders')
->limit(100)
->all();
foreach ($customers as $customer) {
// 没有任何的 SQL 执行
$orders = $customer->orders;
}
从101次查询减少到2次。
让你的代码更加“简洁”
这里说的简洁并不是说代码量,而是表意。比如下面的代码
// 方式1
if($num > 100){
return 1
}else{
return 2
}
// 方式2
return $num > 100 ? 1 : 2;
代码逻辑很简单的时候我们都喜欢第二种方式,但是如果逻辑复杂些,我更喜欢方式1,虽然它可能很多行,但是表意简洁,你能看懂、他也能看懂。
何苦废了牛劲去写一个自我感觉巨牛逼的表达式那!!!
为视图的PHP代码增加一个try
在写action或模型方法的时候,为了保证代码的稳定性,我们一般都会用try....catch语法结构,但是在yii2的视图内很少有人用,记住,也要用!比如下面这段代码。
// 视图内
<?= \app\components\WechatLangSideMenu::widget();?>
如果上面代码出错了怎么办,我推荐如下方式写
<?php
try {
echo \app\components\WechatLangSideMenu::widget();
}catch(\Exception $e){
// 可以不处理也可以写你自己的错误处理。
}
?>
小心使得万年船。
勿写死,用常量或配置。
有些代码需要一些判断,而判断的参考是某些值,比如下面的代码
if($this->type === 1){
return "文章";
}else if($this->type === 2){
return "专栏";
}
我推荐这样写
if($this->type === Item::ARTICLE_TYPE){
return "文章";
}else if($this->type === Item::TOPIC_TYPE){
return "专栏";
}
ARTICLE_TYPE 和 TOPIC_TYPE是Item模型的两个常量。
使用迁移脚本
我说过了很多次,本篇还是要说一次,对于一个yii2程序的数据库部分请用migration来管理。
并且这些脚本应该一起放到到你的版本控制里,记住,迁移脚本一般包含两个部分。
结构脚本
种子数据的导入
很多人都忽略了第二类。另外在做迁移脚本的时候,如果你的表有前缀,那么在脚本里的写法如下
{{%user}}// discuz_user
时间问题
使用yii2开发mysql类web应用的时候,数据表的时间类字段我们喜欢用时间戳,一般表内都会有记录生成时间和更新时间字段。
对于他们的更新请使用yii2内置的TimestampBehavior行为类,则字段数据的填充我们就无需操心了,如下代码
namespace app\models;
use Yii;
use yii\behaviors\TimestampBehavior;
class Article extends \yii\db\ActiveRecord {
public function behaviors(){
return [
[
'class' => TimestampBehavior::className(),
]
];
}
}
因此在数据表中我推荐时间字段命名规则如下
生成时间 created_at
更新时间 updated_at
这样如上代码就完全够用了,无需指定字段。
记住:去掉在rules内对created_at和updated_at字段required的限制。
是父类还是行为
其实我是不排斥任何一种的,各有利弊吧,父类使用简单但是增加了耦合,行为耦合度低但是配置比直接父类复杂些。
当然从理念上说也有点不同
行为 一些类附加的属性
父类 一些类共同的属性
我的用法(不一定就是对的),尤其在模块中我喜欢为控制器增加一层父类。
开发完
程序开发完还需要对yii2程序进行一些配置,很多你一定已经会了。
入口文件
我们首先要改变yii2的运行模式,从开发模式变为生产模式,一般代码如下
// index.php
defined('YII_DEBUG') or define('YII_DEBUG', false);
defined('YII_ENV') or define('YII_ENV', 'prod');
报错页面
对于一个稳定的程序,报错不要紧,要紧的是报错后的处理,既然用户觉得有好又对开发人员有帮助,我之前写过一篇文章,你可以看下《用yii2实现youtube风格的错误处理页面》
urlManager
严格来说这个应该在开发阶段做,为了对搜索引擎更有好,也为了增加程序的安全性,我们应该对url进行美化,比如
/index.php?r=admin/user/index // 写成 /admin/user-index.html