多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 细节注意 一些重要但是容易被忽略的细节记录。不起眼但很重要的知识。 ![](http://cdn.aipin100.cn/微信图片_20190316074725.jpg) 家里的电器坏了,90%的原因是因为没有插电源插头。 写代码也是这样,很多时候往往是因为不够细心而出了问题,比如写错变量名、多了空格等,所以出现问题时最应该先细心的检查一遍代码在做调试。 ***** ### 判断细节 ```php if (0 == '') { echo "output"; } if (0 == 'str') { echo "output"; } if (0 == '1') { echo "not output"; } if (0 == '1str') { echo "not output"; } if ('0' == '') { echo "not output"; } if ('0' == 'str') { echo "not output"; } ---- // 整形转换实验 var_dump((int) '01'); // 1 var_dump((int) '0 1'); // 0 var_dump((int) '0str'); // 0 var_dump((int) '0 str'); // 0 if (0 == '01') { echo "no output"; } if (0 == '0str') { echo "output"; } if (0 == '0 1') { echo "output"; } if (0 == '0 str') { echo "output"; } ``` 字符串 `'0'` 和整数 `0` 是不一样的,在开发中要尤其注意这个容易忽略的细节。 字符串类型 与 整形 作比较时,字符串会自动转换成 整形 在与之比较,**所以 开发时严格要求必须使用 全等比较,不要使用自动转换的特性,除了提高性能外最重要的是能够避免这种不易发现的安全问题。** ---- ### 判断尽量使用全等,不要让其自动转换布尔再比较 php7在线执行 http://www.dooccn.com/php7/ ```php var_dump((bool) 0); // false var_dump((bool) -1); // true var_dump((bool) '-1'); // true var_dump((bool) '0'); // false var_dump((bool) 'str'); // true if (0 == '') { echo 's'; // output } if (0 == 'str') { echo 's'; // output } if ('0' == '') { echo 's'; // no output } ``` 两个类型不同的表达式进行比较时,会先各自自动换成布尔值后在进行比较,**只有当两者类型不一致时才会发生类型自动转换。** >[tip] 当你要用比较时,永远优先考虑使用全等式。 尽量只让类型确定的表达式参与比较,类型不同时也可以手动转换成一致类型后再全等比较,而不要依赖自动的布尔转换,这样做的原因不仅仅是因为自动转换的性能消耗问题,更重要的是为了避免某些隐蔽的错误发生,使程序更加健壮。 >[tip] 纠正:上面说,类型不同的表达式比较时会自动转换为布尔值,这是不完全正确的,应该为:和布尔值比较时 转换为布尔值,和整形比较时,转换为整形后再比较。 ---- ### php与js关于判断的区别 ```php if ('string' == 0) { echo "output"; } ``` js版本:比较上PHP和js是不同的,这有点出乎我的意料 javascript: ```javascript var a = 'string'; a == 0; // false ``` ***** ### 使用不存在变量报错的细节 使用不存在的变量会报错,但有一种情况例外: ```php echo $a; // 报错 Notice: Undefined variable: a $a = null; echo $a['k']; // 不会报错,值为null 或者 $a = false; echo $a['k']; // 不会报错,值为null ``` 这个细节很重要,因为在严格框架下面,我们总会期望当使用不存在的变量时来抛出异常,如果业务逻辑依赖于此,就得小心了。 ***** #### MYSQL decimal(10,2) 四舍五入问题 ```php # number_format 也会四舍五入 echo number_format(12.088, 2, '.', ''); // 12.09 // 还指望mysql自动保留两位小数,不进行任何四舍五入呢,没想到在 MySQL5.5.53 版本下,会进行四舍五入(12.088 => 12.09),坑啊!看来任何时候都不要指望和依赖外部啊。(目前发现不论什么版本的MySQL 都会直接四舍五舍的) // 金额格式化(保留两位小数,四舍五舍) function priceFormat($val) { // 保留两位小数不四舍五入 return substr(sprintf("%.3f", $val), 0, -1); } ``` >[danger] 任何时候都不要指望和依赖外部,所有数据直接算好,交给数据库原原本本的存就可以,不要让数据库参与任何的逻辑部分,以及数据处理。 所以需要自己实现一个,但这也引发一个思考,那就是业务中到底需要保留多少位小数,以及如何对待多余的小数位。最简单粗暴的方式就是 四舍五入 或 四舍五舍,不同交易场景下,这两种方案都有不同的副作用,比如少收或多给。复杂的可能需要考虑银行家算法。 ***** #### 浮点数计算问题 [php浮点数的精度问题深究 - php小松 - CSDN博客](https://blog.csdn.net/a454213722/article/details/52135462) [PHP浮点数的一个常见问题的解答 | 风雪之隅](http://www.laruence.com/2013/03/26/2884.html) [关于PHP浮点数你应该知道的(All 'bogus' about the float in PHP) | 风雪之隅](http://www.laruence.com/2011/12/19/2399.html) [intval遇到小数为什么会减1-CSDN论坛](https://bbs.csdn.net/topics/390789758) [PHP: BC 数学 函数 - Manual](https://www.php.net/manual/zh/ref.bc.php) [PHP: GMP 函数 - Manual](https://www.php.net/manual/zh/ref.gmp.php) ```php // 安全的数字计算方式 // https://www.cnblogs.com/phpfensi/p/8143367.html // https://www.cnblogs.com/jiqing9006/p/5531687.html // http://php.net/manual/zh/function.bcdiv.php // https://blog.csdn.net/LJFPHP/article/details/82255389 // https://www.cnblogs.com/phpper/p/7664069.html //$m和$n代表传入的两个数值,主要就是这两个数值之间的比较 //$x代表传入的方法,比如是;add,sub等 //$scale 代表传入的小数点位数。这个根据需求更改即可 function calc($m, $n, $x = 'add', $scale = 2) { $errors = array( '被除数不能为零', '负数没有平方根', ); switch ($x) { case 'add': $t = bcadd($m, $n, $scale); break; case 'sub': $t = bcsub($m, $n, $scale); break; case 'mul': $t = bcmul($m, $n, $scale); break; case 'div': if ($n != 0) { $t = bcdiv($m, $n, $scale); } else { return $errors[0]; } break; case 'pow': $t = bcpow($m, $n, $scale); break; case 'mod': if ($n != 0) { $t = bcmod($m, $n, $scale); } else { return $errors[0]; } break; case 'sqrt': if ($m >= 0) { $t = bcsqrt($m); } else { return $errors[1]; } break; } return $t; } ``` ***** #### tp $db->find(null) 注意 ```php Db::name('user')->find($userId); ``` 如果 `$userId` 是 `null` ,那么 `find(null)` 会查询出来表的第一条数据,由于我们的疏忽,没有 `(int) $userId` ,很可能就造成业务逻辑不符合预期,甚至引起严重而隐秘的BUG。 突然感觉到参数类型严格限制语言的好处了,确实能在很大程度上帮助我们在开发时避免这类错误问题。 ***** ### intval() 整形转换问题 > 不管变量前面有多少个0,且数字都小于8,它会当作是八进制数转换成十进制数 [php中intval()函数 - ann_glx - 博客园](https://www.cnblogs.com/anns/p/3494195.html) ***** #### PHP intval() 处理大整形问题 可以使用: ```php $paysn = floatval($_POST['paysn']); ``` 不能在使用 `intval()`了 [php关于数字防注入,intval溢出,intval - u010412301的博客 - CSDN博客](http://blog.csdn.net/u010412301/article/details/55046733) [PHP长整型在32位系统中强制转化溢出 - CleverCode的博客 - CSDN博客](http://blog.csdn.net/clevercode/article/details/46423103) [PHP-php使用intval长度超限的问题? - 德问:编程社交问答](http://www.dewen.net.cn/q/3969) last update:2018-2-9 10:48:00 ***** ### 条件判断优先级问题之括号 ```php // 错误,不符合预期,且不易发现 if (!$info = $db->lock(true)->getRow($sql) || $info['s_patent_claim_status'] != 0) {} // 正确,符合预期 if (!($info = $db->lock(true)->getRow($sql)) || $info['s_patent_claim_status'] != 0) {} // 正确,符合预期 if ($patentId && $recordInfo = $db->lock(true)->getRow($sql)) {} ``` 所以必须细心谨慎对待这类条件判断问题,越不起眼越往往越容易出错,要确保每个功能上线前都通过完备了测试。 ***** ### mysql非严格模式下注意的细节 ``` `status` tinyint(4) unsigned NOT NULL DEFAULT 0 COMMENT '' ``` unsigned 无符号的,在非严格模式下, `SET status = -1` 不会更改任何数据,也不会报错,这时就需要注意了。 需要详细的测试,程序要健壮,一定要在严格模式环境下进行开发,对返回进行检查,及时发现问题。 ***** ### var_export对标量不友好,特别是浮点型 ```php echo var_export(1.4, true); // 1.3999999999999999 echo var_export('1.4', true); // '1.4' ``` 所以如果用到了var_export, 请判断一下是否为标量: ```php if (!is_scalar($value)) { $val = var_export($value, true); } else { $val = $value; } ``` ***** ### @ 关键字慎用!!! @ 屏蔽错误显示,页面出错不会往下执行,但是页面不会显示任何报错信息!这让人很无语,如果不是确定的代码,请不要使用@ 不然没有错误信息怎么调试呢,错误也很重要,要知道错误、BUG也是程序的一部分,也和程序本身一样的同等重要,没有错误信息、错误处理的程序是不完整的,是没有灵魂的。 ---- ### 计算时注意数据类型是否为数字 这是js中的情况: ```javascript 1 + '9' // "19" ``` php也有类似的问题,总之任何时候不要忘记,当你想要计算时,是否严格验证了计算对象的数据类型。 ---- ### left join 左连一对多问题 a LEFT JOIN b ON a.id = b.mid a INNER JOIN b ON a.id = b.mid 如果 a只有一条,但是对应的 b有两条,**那么最终结果是两条** ,这点容易让人忽略掉 |id|name| |---|---| |10| name | |id|mid|title| |---|---|---| |1|10| title1 | |2|10| title2 | >[tip] 如果右表 `ON` 外键字段有重复的,那么就会出现重复数据 ~~~ 1. select DISTINCT a.id, a.name, b.* from a left join b on a.id = b.mid 重复 2. select DISTINCT a.id, a.name from a left join b on a.id = b.mid 不重复 3. select a.id, a.name from a left join b on a.id = b.mid GROUP BY a.id 不重复 4. select group_concat(DISTINCT a.id) as id,a.name, b.* from a left join b on a.id = b.mid 不重复 https://blog.csdn.net/u010003835/article/details/79154457 DISTINCT 表示对后面的所有参数的拼接取 不重复的记录,相当于 把 SELECT 表达式的项 拼接起来选唯一值。 即:行唯一,所以 上面 1 还是重复,2 不重复 ~~~ [https://segmentfault.com/a/1190000017067294](https://segmentfault.com/a/1190000017067294) [数据库表连接的简单解释 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2019/01/table-join.html) [数据库的最简单实现 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2014/07/database\_implementation.html) ![](http://cdn.aipin100.cn/7ded569002d08f20605acf021f7cb979) ps: 待研究 on 和 where 的区别,对上面的情况来说理论上 where 写 on 效果也是一样的,甚至提前缩小了范围表连接会更小 on a.id = b.mid where a.id = 1 表关系:条件两边都是表的字段,而不是其它值,如:a.id = b.mid 使用表连接,表关系不可无,并且 on 中只有写 表关系 才有效,如果写了 where 条件 会直接被忽略掉。 需要注意的是,表关系 也可以写在 where 中,但是不建议这样,应该都写在 on 中提前缩小表连接范围。 上面说法错误,正确如下: 1. 左联或右连时 on 都不会缩小主表范围,内联可以缩小范围。 2. on 只是查找副表数据与其连接,副表没有数据不会影响结果(副表字段都是 null)。 3. where 是最终对数据行进行过滤 4. ~~on 上写主表条件没有作用(因为这个条件只是查找副表数据)~~,但可以写副表 条件 缩小 副表范围 5. ~~如果要过滤最终结果,只能依靠 where~~ 6. where/join on 在 INNER/RIGHT 时没什么区别,但是 LEFT 时就有很大区别:on 上只能过滤 副表的数据,并不能像 where 一样 过滤最终数据,导致如果主表数据多,最终结果可能不能如愿。 ~~~ join on 不同类型的字段比较时,如 int 与 var 比较时,会自动转为 整形再比较,这就导致了 结果并不全等 如 1q 1_2 都是当做1来比较的 https://blog.csdn.net/u013378306/article/details/105110465/ ~~~ ---- ### js篇:不要使用 “连相等赋值” ```javascript function a() { var b = d = 1; } a(); d; // 1 // d 成了全局的了,如果你想将d赋值给b,就不要这样写 “连相等赋值”,而是这样: function a() { var d = 1, b = d; } a(); ``` ---- ### mysql_insert_id() 受 insert 和update影响 所以 mysql_insert_id 不一定是取到 最后 insert 的id,如果中间 有update,则返回0 。 ***** ### 注意隐含产生的引用 ```php $arr = [['a'], ['b']]; var_dump($arr); foreach ($arr as &$item) { foreach ($item as &$value) { } // unset($value); } // unset($item); var_dump($arr); ``` ``` array(2) { [0]=> array(1) { [0]=> string(1) "a" } [1]=> array(1) { [0]=> string(1) "b" } } array(2) { [0]=> array(1) { [0]=> string(1) "a" } [1]=> &array(1) { [0]=> &string(1) "b" } } ``` ---- ### mysql 千万不要使用 id != null SELECT * FROM `sp_led_mould` WHERE id != null; 没有结果 SELECT * FROM `sp_led_mould` WHERE id is not NULL; 才会有结果 mysql version: 5.6.16-log ---- ### 注意不要 在 tp 同一模型上 上做多次更新 [更新 · ThinkPHP5.0完全开发手册 · 看云](https://www.kancloud.cn/manual/thinkphp5/135189) > 注意不要在一个模型实例里面做多次更新,会导致部分重复数据不再更新,正确的方式应该是先查询后更新或者使用模型类的`update`方法更新。 ~~~ 关于多次调用save更新只有第一次更新的数据成功,后面都不成功的,可以在调用save前调用->force()来强制更新,或者调用update更新但是不要调用where方法。调用force的原因是save更新后会$this->origin = $this->data;而更新时又会调用getChangedData检查这个属性,不调用force就会只更新上次没更新过的字段,所以才会导致循环save只有第一条成功。模型的update就不会有这个问题,因为这个方法每次都是重新new一个实例 ---- foreach(\[1,2,3\] as $k=>$v){ $user->save($data,\[id=>$v\]); } foreach更新只能更新第一条数据,什么原因? ~~~ ---- ### json_decode 问题 **浮点精度** php.ini 建议配置: ~~~ serialize_precision: 16 serialize_precision: -1 ~~~ 否则会导致 json_encode 中浮点数出现精度问题 ---- **大整形问题** 解决大整形数值会被转成 科学计数法 string 类型: ```php $res = json_decode($body, true, 512, JSON_BIGINT_AS_STRING); ``` ---- last update: 2019-5-28 23:42:01