开发维护PHP项目时的问题总结
由于工作原因,不得不接触一些古早代码,对其进行拓展和维护,在此期间碰到了许多坑坑绕绕。在这里记录一下,产生的效果和对应的解决方案与避免方法。
关联数组相关问题
PHP 的「对象」或「结构体」概念的相关物是 array,但是 PHP 的 array 可以是正常数组,也可以是关联数组,在关联数组的时候,可以把它和 golang 中的 map 类比,但是它们也有不少的差异。
Undefined Array Key 问题
正确姿势
在 PHP 中,如果一个关联数组使用如下:
$object = [
'key_1' => 'value1',
'key_2' => 'value2',
];
$key_1 = $object['key_1'];
$key_2 = $object['key_2'];
echo $key_1."\n";
echo $key_2."\n";
那么 PHP 在执行时可以正确处理并给出输出:
value1
value2
错误姿势
但如果我们这么使用:
$object = [
'key_1' => 'value1',
'key_2' => 'value2',
];
$key_1 = $object['key_1'];
$key_2 = $object['114514'];
echo $key_1."\n";
echo $key_2."\n";
这个时候 PHP 就要发怒了:
PHP Warning: Undefined array key "key_3" in /path/to/php/test.php on line xxx
Warning: Undefined array key "key_3" in /path/to/php/test.php on line xxx
如果执行等级比较高的话,这个错误会直接导致整个链路被 panic 掉而无法执行下面的逻辑。
我们可以通过下面的方式来防御性编程:
$object = [
'key_1' => 'value1',
'key_2' => 'value2',
];
$key_1 = $object['key_1'];
$key_2 = $object['114514'] ?? '1919810';
echo $key_1."\n";
echo $key_2."\n";
这样就可以给不存在的字段在获取不到时设定一个初始值了。
总结
PHP 的关联数组在取不到对应的「字段」时,既不会返回空,也不会返回 null,而是会直接 warning 报错,(如果执行等级较低的话,就会返回 null),需要和 go 的 map 做出区分。
数组空字段问题
判断数组空字段
如同上文我们提到了,我们可以用 ??
运算符来判断一个数组的某个字段是否为空,那么我们那可能在实际会这么使用:
$str = '{"key_1": "value_1", "key_2": ""}';
$object = json_decode($str, true);
echo $object['key_2'] ?? "empty";
如果对方没有对 key_2 设置 omitempty 的话,那么 json 就会如上面这样,此时如果我们直接使用 ??
来判断字段是否为空,那么我们得到的就是一个空字符串,并不符合我们的期望,此时我们需要:
$str = '{"key_1": "value_1", "key_2": ""}';
$object = json_decode($str, true);
echo empty($object['key_2'] ?? '') ? "empty" : $object['key_2'];
此时就会如约输出 empty
了。
空数组附加问题
我们有的时候会将某个关联数组序列化输出,比如:
$object = ['key' => 'value'];
$response = json_encode($object);
echo $response."\n";
直接运行我们就会得到:
{"key":"value"}
但是如果我们需要输出的字段包括一个空结构的话,比如:
$object = [
'error_code' => 114514,
'error_msg' => 'too many requests',
'data' => [],
];
$response = json_encode($object);
echo $response."\n";
以我们朴素的肉编器人肉编译器来看,它的结果应当是:
{"error_code": 114514, "error_msg": "too many requests", "data": {}}
但实际上,它的结果却是:
{"error_code":114514,"error_msg":"too many requests","data":[]}
data
字段是一个 数组 (咬牙切齿)。如果我们在 golang 中这么接 PHP 返回的答辩:
type Response struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
Data PayloadStruct `json:"data,omitempty"`
}
那么 go 就 亲切 地给你奖励:
Unmarshal json into struct: cannot unmarshal array into Go value
这个问题目前没有什么好的解决方案,只能通过 json.RawMessage
这种结构先把 data 接下来,然后使用额外的判断逻辑能否 unmarshal。
由此衍生的问题有:
1. 计算一个 json 对象的签名不一致
2. 必填字段校验逻辑复杂
语法相关易错点
使用未声明变量
此 topic 记录于2024年4月22日,首次遇到于2024年4月19日
在正常情况下,在PHP中使用未声明的变量,IDE(PhpStorm)会给出报错,比如下面的代码:
private function _error_func(): void
{
echo $undefined_var;
}
IDE就会把 $undefined_var
给下划线标红,表示这是一个未声明、未定义的变量,但是在某些语法条件下,IDE会忽略掉这种情况:
private function _sample_func(): void
{
if (!empty($undefined_var)) {
echo 'defined';
}
}
在这种情况下,IDE不会把 $undefined_var
标红并报错,因为 empty($undefined_var)
是一个合法的表达式,即使 if 表达式内的分支永远都不会进入。