PHPer这样写代码也许更优雅

前言

转眼间成为一名PHPer已经快整整两年了,在这期间也对如何写出可读性高,便于扩展的代码有了一些本身的想法。数据库

使用引用

场景一:遍历一个数组获取新的数据结构编程

也许你会这样写:设计模式

// 申明一个新的数组,组装成你想要的数据
$tmp = [];
foreach ($arr as $k => $v) {
    // 取出你想要的数据
    $tmp[$k]['youwant'] = $v['youwant'];
    ...
    // 一系列判断获得你想要的数据
    if (...) {
        $tmp[$k]['youwantbyjudge'] = 'TIGERB';
    }
    ...
}
// 最后得要你想要的数组$tmp

-------------------------------------------------------

// 也许你觉着上面的写法不是很好,那咱们下面换种写法
foreach ($arr as $k => $v) {
    // 一系列判断获得你想要的数据
    if (...) {
        // 复写值为你想要的
        $arr[$k]['youwantbyjudge'] = 'TIGERB'
    }
    ...
    // 干掉你不想要的结构
    unset($arr[$k]['youwantdel']);
}
// 最后咱们获得咱们的目标数组$arr
复制代码

接下来咱们使用引用值:数组

foreach ($arr as &$v) {
    // 一系列判断获得你想要的数据
    if (...) {
        // 复写值为你想要的
        $v['youwantbyjudge'] = 'TIGERB'
    }
    ...
    // 干掉你不想要的结构
    unset($v['youwantdel']);
}
unset($v);
// 最后咱们获得咱们的目标数组$arr
复制代码

使用引用是否是使咱们的代码更加的简洁,除此以外相对于第一种写法,咱们节省了内存空间,尤为是再操做一个大数组时效果是及其明显的。bash

场景二:传递一个值到一个函数中获取新的值antd

基本和数组遍历一致,咱们只须要声明这个函数的这个参数为引用便可,以下:数据结构

function decorate(&$arr = []) {
    # code...
}

$arr = [
    ....
];
// 调用函数
decorate($arr);
// 如上即获得新的值$arr,好处仍是节省内存空间

复制代码

使用try...catch...

假若有下面一段逻辑:函数

class UserModel
{
    public function login($username = '', $password = '')
    {
        code...
        if (...) {
            // 用户不存在
            return -1;
        }
        code...
        if (...) {
            // 密码错误
            return -2;
        }
        code...
    }
}

class UserController
{
    public function login($username = '', $password = '')
    {
        $model = new UserModel();
        $res   = $model->login($username, $password);
        if ($res === -1) {
            return [
                'code' => '404',
                'message' => '用户不存在'
            ];
        }
        if ($res === -2) {
            return [
                'code' => '400',
                'message' => '密码错误'
            ];
        }
        code...
    }
}
复制代码

咱们用try...catch...改写后:ui

class UserModel
{
    public function login($username = '', $password = '')
    {
        code...
        if (...) {
            // 用户不存在
            throw new Exception('用户不存在', '404');
        }
        code...
        if (...) {
            // 密码错误
            throw new Exception('密码错误', '400');
        }
        code...
    }
}

class UserController
{
    public function login($username = '', $password = '')
    {
        try {
            $model = new UserModel();
            $res   = $model->login($username, $password);
            // 若是须要的话,咱们能够在这里统一commit数据库事务
            // $db->commit();
        } catch (Exception $e) {
            // 若是须要的话,咱们能够在这里统一rollback数据库事务
            // $db->rollback();
            return [
                'code'    => $e->getCode(),
                'message' => $e->getMessage()
            ]
        }
    }
}
复制代码

经过使用try...catch...使咱们的代码逻辑更加清晰,try...里只须要关注业务正常的状况,异常的处理统一在catch中。因此,咱们在写上游代码时异常直接抛出便可。this

使用匿名函数

** 构建函数或方法内部的代码块 **

假如咱们有一段逻辑,在一个函数或者方法里咱们须要格式化数据,可是这个格式化数据的代码片断出现了屡次,若是咱们直接写可能会想下面这样:

function doSomething(...) {
    ...
    // 格式化代码段
    ...
    ...
    // 格式化代码段[重复的代码]
    ...
}
复制代码

我相信大多数的人应该不会像上面这么写,可能都会像下面这样:

function doSomething(...) {
    ...
    format(...);
    ...
    format(...);
    ...
}

// 再声明一个格式花代码的函数或方法
function format() {
    // 格式化代码段
    ...
}
复制代码

上面这样的写法没有任何的问题,最小单元化咱们的代码片断,可是若是这个format函数或者方法只是doSomething使用呢?我一般会像下面这么写,为何?由于我认为在这种上下文的环境中format和doSomething的一个子集。

function doSomething() {
    ...
    $package = function (...) use (...) { // 一样use后面的参数也能够传引用
        // 格式化代码段
        ...
    };
    ...
    package(...);
    ...
    package(...);
    ...
}
复制代码

** 实现类的【懒加载】和实现设计模式的【最少知道原则】 **

假若有下面这段代码:

class One
{
    private $instance;

    // 类One内部依赖了类Two
    // 不符合设计模式的最少知道原则
    public function __construct()
    {  
        $this->intance = new Two();
    }

    public function doSomething()
    {
        if (...) {
            // 若是某种状况调用类Two的实例方法
            $this->instance->do(...);
        }
        ...
    }
}
...

$instance = new One();
$instance->doSomething();
...
复制代码

上面的写法有什么问题?

  • 不符合设计模式的最少知道原则,类One内部直接依赖了类Two
  • 类Two的实例不是全部的上下文都会用到,因此浪费了资源,有人说搞个单例,可是解决不了实例化了不用的尴尬

因此咱们使用匿名函数解决上面的问题,下面咱们这么改写:

class One
{
    private $closure;

    public function __construct(Closure $closure)
    {  
        $this->closure = $closure;
    }

    public function doSomething()
    {
        if (...) {
            // 用的时候再实例化
            // 实现懒加载
            $instance = $this->closure();
            $instance->do(...)
        }
        ...
    }
}
...

$instance = new One(function () {
    // 类One外部依赖了类Two
    return new Two();
});
$instance->doSomething();
...
复制代码

减小对if...else...的使用

若是你遇见下面这种类型的代码,那必定是个黑洞。

function doSomething() {
    if (...) {
        if (...) {
            ...
        } esle {
            ...
        }
    } else {
        if (...) {
            ...
        } esle {
            ...
        }
    }
}

复制代码

** 提早return异常 **

细心的你可能会发现上面这种状况,可能绝大多数else代码里都是在处理异常状况,更有可能这个异常代码特别简单,一般我会这么去作:

// 若是是在一个函数里面我会先处理异常的状况,而后提早return代码,最后再执行正常的逻辑
function doSomething() {
    if (...) {
        // 异常状况
        return ...;
    }
    if (...) {
        // 异常状况
        return ...;
    }
    // 正常逻辑
    ...
}

// 一样,若是是在一个类里面我会先处理异常的状况,而后先抛出异常
class One
{
    public function doSomething()
    {
        if (...) {
            // 异常状况
            throw new Exception(...);
        }
        if (...) {
            // 异常状况
            throw new Exception(...);
        }
        // 正常逻辑
        ...
    }
}

复制代码

** 关联数组作map **

若是咱们在客户端作决策,一般咱们会判断不一样的上下文在选择不一样策略,一般会像下面同样使用if或者switch判断:

class One
{
    public function doSomething()
    {
        if (...) {
            $instance = new A();
        } elseif (...) {
            $instance = new A();
        } else {
            $instance = new C();
        }
        $instance->doSomething(...);
        ...
    }
}
复制代码

上面的写法一般会出现大量的if语句或者switch语句,一般我会使用一个map来映射不一样的策略,像下面这样:

class One
{
    private $map = [
        'a' => 'namespace\A', // 带上命名空间,由于变量是动态的
        'b' => 'namespace\B',
        'c' => 'namespace\C'
    ];
    public function doSomething()
    {
        ...
        $instance = new $this->map[$strategy];// $strategy'a''b''c'
        $instance->doSomething(...);
        ...
    }
}
复制代码

使用接口

为何要使用接口?极大的便于后期的扩展和代码的可读性,例如设计一个优惠系统,不一样的商品只是在不一样的优惠策略下具有不一样的优惠行为,咱们定义一个优惠行为的接口,最后对这个接口编程便可,伪代码以下

Interface Promotion
{
    public function promote(...);
}

class OnePromotion implement Promotion
{
    public function doSomething(...)
    {
        ...
    }
}

class TwoPromotion implement Promotion
{
    public function doSomething(...)
    {
        ...
    }
}
复制代码

控制器拒绝直接的DB操做

最后我想说的是永远拒绝在你的Controller里直接操做DB,为何?咱们的程序绝大多数的操做基本都是增删改查,多是查询的where条件和字段不一样,因此有时候咱们能够抽象的把对数据库增删改查的方法写到model中,经过参数暴露咱们的where,fields条件。一般这样能够很大程度的提升效率和代码复用。好比像下面这样:

class DemoModel implement Model
{
    public function getMultiDate($where = [], $fields = ['id'], $orderby = 'id asc')
    {
        $this->where($where)
             ->field($fields)
             ->orderby($orderby)
             ->get();
    }
}
复制代码

最后

若是有写的不对的地方,欢迎你们指正,THX~

相关文章
相关标签/搜索