内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

PHP开发拾遗0x01

2018-02-13 17:09 出处:清屏网 人气: 评论(0

提纲:

  • 请使用 const 常量
  • 再说 = == ===
  • 原生函数 json_encode()/array_merge()/preg_match_all()
  • 一次「压平」 if 的踩坑记录
  • 简单的「频次限制」, 常见场景比如重复点击重复请求

请使用 const 常量

其实开始我是「拒绝」的, 理由是增加了一层 映射 , 就是变相的增加了一层 复杂度 , 比如最常见的常量应用场景, 表示各种状态:

const STATUS_UNDO = 'undo';
const STATUS_DOING = 'doing';
const STATUS_SUCCESS = 'success';
const STATUS_FAIL = 'fail';

之前一直抱有的观点是, 记忆一次 undo/doing/success/fail 就够了, 没必要 常量 再来一层, 用的地方保持就好了.

但最近几次密集的使用常量, 让我改变了这个想法 -- 常量是可以 IDE 提示的. 由于编程是一件 精确 的活, 原来的方式需要 精确记忆细节 或者 复制粘贴 , 有了 IDE 提示后就简单多了.

同时再想想下面的场景:

  • 如果字符串不是这么简单, 有点长(这种情况太多了), 经常在 array key 类似的场景用到
  • 如果这个字段是数据表中映射出来的, 有十多个类似字段
  • 如果这个表示状态用的数字, 比如 0-undo, 1-doing, 2-success, 3-fail, 就需要在使用的地方带上注释了
if (3 == $status) { // 失败状态
    // fail case
} else if (2 == $status) {
    // success case
}

综上, 常量其实一件 省事 的事儿

再说 = == ===

上面的代码其实已经示范了一个例子, === 是初学很容易遇到的 困惑 , 这里再简单重申一下定义:

  • = : 赋值语句, 给变量赋值
  • == : 判断是否 相等
  • === : 判断是否 全等 , 区别与 == 的是要求 数据类型一致

理解清楚定义, 然后再看 2 个场景:

if ($status = 1) { // 如果这里把 == 少写了
    //
} else {
    //
}

上面的错误基本每个人都犯过吧, 尤其是是使用 if ($var = xxx) 确实有另外一个用途:

$var = 'xxx'; // 给 $var 赋值
if ($var) {
    //
}

if ($var = 'xxx') { // 常见的缩写方式
    //
}

有效避免这种错误的方式:

if (1 == $status) { // 如果少写了 =, IDE会自动提示
    //
} else {
    //
}

推荐这样的写法, 因为最近一次 bug 就是这个问题导致的, 指不定哪个 夜黑风高 的晚上, 又写出这种 bug 出来.

再来说说 ===== :

if (strpos('abc', 'a')) { // 判断字符串是否存在
    echo 'yes';
} else {
    echo 'no';
}

这里明显是个错误的例子, 因为 strpos() 函数返回的是匹配到的 起始位置 , 即 int 0 , 不匹配时返回 bool false , 正确的做法应该是:

if (strpos('abc', 'a') !== false) {
    echo 'yes';
} else {
    echo 'no';
}

===== 的关键点就在于数据类型上, 弱类型是 对人友好 , 强类型是 对机器友好 .

原生函数 json_encode()/array_merge()/preg_match_all()

接着上面的 strpos() 继续聊几个原生函数.

因为 json 的 大行其道 , json_encode()/json_encode() 就会经常使用到了, 直接说几个要点:

  • 需要 ext-json 扩展支持, 不过 PHP 默认是开启这个扩展的, 所以发现用不了的时候不要大呼 见鬼了
  • json 数据类型: bool int string array object, 因为 PHP 的弱类型, 带来几个需要注意的类型转换的问题:
json_encode(1); // int <-> string
json_encode('1');

json_encode(true); // bool <-> string
json_encode('true');

echo json_encode(['a' => '']); // 空字符串: {"a":""}
echo json_encode(['a' => []]); // 空数组: {"a":[]}
echo json_encode(['a' => new \Stdclass()]); // 空对象: {"a":{}}

尤其要注意这里的 new \Stdclass() , 毕竟 PHP 编程中, 经常是只使用 array 这一种数据结构.

这也是为什么要使用 json_decode($str, true) 的原因, 默认返回 Stdclass 类型, 带 true 参数才是 array 类型

  • Unicode转义
echo json_encode('中国'); // "\u4e2d\u56fd"
echo json_encode('中国', JSON_UNESCAPED_UNICODE); // "中国"

这样就不用拿到之后还要 decode 一下了, 而且不转义下需要传输的 字节数 减少了其实性能更好, 具体可以参考鸟哥的 blog

array_merge() 的坑不知道有多少人踩过, 在 PHP manual 上是有说的: 不能递归合并 , 所以很多框架都提供了辅助函数来处理:

// 比如 yii 框架的 \yii\helpers\BaseArrayHelper::merge()
public static function merge($a, $b)
{
    $args = func_get_args();
    $res = array_shift($args);
    while (!empty($args)) {
        $next = array_shift($args);
        foreach ($next as $k => $v) {
            if ($v instanceof UnsetArrayValue) {
                unset($res[$k]);
            } elseif ($v instanceof ReplaceArrayValue) {
                $res[$k] = $v->value;
            } elseif (is_int($k)) {
                if (isset($res[$k])) {
                    $res[] = $v;
                } else {
                    $res[$k] = $v;
                }
            } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
                $res[$k] = self::merge($res[$k], $v);
            } else {
                $res[$k] = $v;
            }
        }
    }

    return $res;
}

preg_match_all() 是正则匹配, 比较适合日常使用了, 这里简单mark一下:

preg_match_all("/href='(.*?)'/", $str, $output);
// $output[0] 返回所有满足整段正则的字符串
// $output[1] 开始以此返回 () 中匹配到的值, 类似 perl 中的 $1, $2...

一次「压平」 if 的踩坑记录

首先是一个简单风格的对比:

// if-else
if ('a' == $a) {
    //
} else {
    //
}

// 压平一点
$a = 'xxx';
if ('a' == $a) {
    //
}

个人倾向于后一种, 这样可以只有考虑一次 if , 当然具体情况要具体分析.

来看看具体的采坑记:

function checkStatus() { // 读取配置请检查条件是否满足
    $a = getConfig(); // 获取配置

    // 基础条件: 任一一个不满足就返回 false
    if ($a['base']) {
        foreach ($a['base'] as $v) {
            if (!$v) {
                return false;
            }
        }
    }

    // 附加条件: 满足基础条件的情况下, 还需要满足附加中的一项
    if ($a['option']) {
        foreach ($a['option'] as $v) {
            if (!$v) {
                return true;
            }
        }
    }

    return false;
}

这是初版的代码, 基础条件附加调价 都是可配置的, 如果没有配置 附加条件 , 就出现了 2 个问题:

  • 没有对变量值进行检测, 尤其是 array 的 key, 这是 PHP 中 非常常见 的错误
  • 只是简单的改为 if (isset($a['option']}) , 恭喜你, 逻辑错误 , 这也是 根据报错改代码 容易遇到的问题

正确的版本:

function checkStatus() { // 读取配置请检查条件是否满足
    $a = getConfig(); // 获取配置

    // 基础条件: 任一一个不满足就返回 false
    if ($a['base']) {
        foreach ($a['base'] as $v) {
            if (!$v) {
                return false;
            }
        }
    }

    // 附加条件: 满足基础条件的情况下, 还需要满足附加中的一项
    if (empty($a['option'])) {
        return true; // 通过了基础条件, 到这里就需要返回 true
    }
    foreach ($a['option'] as $v) { // 判断附加条件
        if (!$v) {
            return true;
        }
    }

    return false;
}

这里提醒 2 点:

  • 使用 isset() / empty() 来进行变量检测
  • 尽管大部分情况下, 写业务 看起来就是写 if-else , 但是请务必小心, 随着复杂度提升, 很容易出错的

简单的「频次限制」

常见场景: 防止页面重复点击后端重复处理, 加入 60s 点击限制

先来看最终结果:

if (1 == MyRedis::incr(MyRedis::CLICK_ITEM_A)) {
    MyRedis::expire(MyRedis::CLICK_ITEM_A, 60); // 60s 过期时间
    // 业务逻辑
}

MyRedis() 类是使用 facade 设计模式, 对 exe-redis 扩展的封装, 这样业务不用关心 redis client 初始化的相关的细节:

// ext-redis 初始化的相关细节
$redis = new \Reids();
$redis->connct('127.0.0.1');
$redis->auth('password')
$redis->select(1);

// 方法参数和返回值 和 ext-redis 扩展保持一致
$redis->incr($key);
MyRedis::incr(MyRedis::CLICK_ITEM_A);

而且自己封装的 MyRedis() 类还可以使用常量, 有效标识出 具体业务

写在最后

细节出魔鬼

practice make perfect

分享给小伙伴们:
本文标签: PHP

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号