## 细节注意
一些重要但是容易被忽略的细节记录。不起眼但很重要的知识。
![](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
- 开始
- 公益
- 更好的使用看云
- 推荐书单
- 优秀资源整理
- 技术文章写作规范
- SublimeText - 编码利器
- PSR-0/PSR-4命名标准
- php的多进程实验分析
- 高级PHP
- 进程
- 信号
- 事件
- IO模型
- 同步、异步
- socket
- Swoole
- PHP扩展
- Composer
- easyswoole
- php多线程
- 守护程序
- 文件锁
- s-socket
- aphp
- 队列&并发
- 队列
- 讲个故事
- 如何最大效率的问题
- 访问式的web服务(一)
- 访问式的web服务(二)
- 请求
- 浏览器访问阻塞问题
- Swoole
- 你必须理解的计算机核心概念 - 码农翻身
- CPU阿甘 - 码农翻身
- 异步通知,那我要怎么通知你啊?
- 实时操作系统
- 深入实时 Linux
- Redis 实现队列
- redis与队列
- 定时-时钟-阻塞
- 计算机的生命
- 多进程/多线程
- 进程通信
- 拜占庭将军问题深入探讨
- JAVA CAS原理深度分析
- 队列的思考
- 走进并发的世界
- 锁
- 事务笔记
- 并发问题带来的后果
- 为什么说乐观锁是安全的
- 内存锁与内存事务 - 刘小兵2014
- 加锁还是不加锁,这是一个问题 - 码农翻身
- 编程世界的那把锁 - 码农翻身
- 如何保证万无一失
- 传统事务与柔性事务
- 大白话搞懂什么是同步/异步/阻塞/非阻塞
- redis实现锁
- 浅谈mysql事务
- PHP异常
- php错误
- 文件加载
- 路由与伪静态
- URL模式之分析
- 字符串处理
- 正则表达式
- 数组合并与+
- 文件上传
- 常用验证与过滤
- 记录
- 趣图
- foreach需要注意的问题
- Discuz!笔记
- 程序设计思维
- 抽象与具体
- 配置
- 关于如何学习的思考
- 编程思维
- 谈编程
- 如何安全的修改对象
- 临时
- 临时笔记
- 透过问题看本质
- 程序后门
- 边界检查
- session
- 安全
- 王垠
- 第三方数据接口
- 验证码问题
- 还是少不了虚拟机
- 程序员如何谈恋爱
- 程序员为什么要一直改BUG,为什么不能一次性把代码写好?
- 碎碎念
- 算法
- 实用代码
- 相对私密与绝对私密
- 学习目标
- 随记
- 编程小知识
- foo
- 落盘
- URL编码的思考
- 字符编码
- Elasticsearch
- TCP-IP协议
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依赖注入
- 开发笔记
- 经纬度格式转换
- php时区问题
- 解决本地开发时调用远程AIP跨域问题
- 后期静态绑定
- 谈tp的跳转提示页面
- 无限分类问题
- 生成微缩图
- MVC名词
- MVC架构
- 也许模块不是唯一的答案
- 哈希算法
- 开发后台
- 软件设计架构
- mysql表字段设计
- 上传表如何设计
- 二开心得
- awesomes-tables
- 安全的代码部署
- 微信开发笔记
- 账户授权相关
- 小程序获取是否关注其公众号
- 支付相关
- 提交订单
- 微信支付笔记
- 支付接口笔记
- 支付中心开发
- 下单与支付
- 支付流程设计
- 订单与支付设计
- 敏感操作验证
- 排序设计
- 代码的运行环境
- 搜索关键字的显示处理
- 接口异步更新ip信息
- 图片处理
- 项目搭建
- 阅读文档的新方式
- mysql_insert_id并发问题思考
- 行锁注意事项
- 细节注意
- 如何处理用户的输入
- 不可见的字符
- 抽奖
- 时间处理
- 应用开发实战
- python 学习记录
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文档相似度验证
- thinkphp5.0数据库与模型的研究
- workerman进程管理
- workerman网络分析
- java学习记录
- docker
- 笔记
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京东
- pc_detailpage_wareBusiness
- doc
- 电商网站设计
- iwebshop
- 商品规格分析
- 商品属性分析
- tpshop
- 商品规格分析
- 商品属性分析
- 电商表设计
- 设计记录
- 优惠券
- 生成唯一订单号
- 购物车技术
- 分类与类型
- 微信登录与绑定
- 京东到家库存系统架构设计
- crmeb
- 命名规范
- Nginx https配置
- 关于人工智能
- 从人的思考方式到二叉树
- 架构
- 今日有感
- 文章保存
- 安全背后: 浏览器是如何校验证书的
- 避不开的分布式事务
- devops自动化运维、部署、测试的最后一公里 —— ApiFox 云时代的接口管理工具
- 找到自己今生要做的事
- 自动化生活
- 开源与浆果
- Apifox: API 接口自动化测试指南