yii2开发中19条推荐实践(阿北总结)

3,438 阅读7分钟

虽然每个人的编程风格不同,但是有些建议能让你的代码更加规范和稳定,本次就我这次网站更新总结如下几点,希望对你的yii2学习和使用有所帮助。

环境说明

  • 服务器环境: 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'

静态缓存问题

在用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

具体关于urlManager的配置及常用web服务器配置可以看下我之前写的速查表,有现成的代码。

小结

一不小心写了这么多,当然yii2开发要注意的地方何止这些,以后慢慢说。