PHP中常见疏漏之处

本文旨在整理 PHP 使用过程中常见容易忽略而导致错误之处,后续会持续补充更新。

同时,也是更深层次理解 PHP 实现原理、设计出发点,其中一些可发现开发者为了满足灵活扩展而专门进行的设计,善用则佳,乱用则损。

PS:没有最好的语言,只有最合适的工具。

类名、方法名不区分大小写

结论:不区分大小写的有如下场景,类名、函数(方法)名不区分大小写,可能要让别人耻笑了。

  • 类名
  • 函数、方法名
  • 魔术常量:__LINE____FILE____DIR____FUNCTION____CLASS____METHOD____NAMESPACE__
  • NULL、TRUE、FALSE
  • 类型强制转换中的类型关键词

Note: Function names are case-insensitive, though it is usually good form to call functions as they appear in their declaration.

声明:

  • 强烈建议遵从统一大小写规范,避免困扰及其他语言切换理解成本。
  • 脚本语言以其灵活、解释执行的特性具有诸多优势,但应充分了解其弊端

参考阅读:PHP大小写敏感问题整理

array_merge()中出现null的影响

结论:array_merge()要求每个参数必须为数组,其中任一参数非数组将导致Warning和结果为NULL
最佳实践:array_merge()前进行非空判断或强制类型转换
反思:弱类型语言,在处理上切记小心类型差异的影响。在PHP7之后的发展趋势上,PHP在逐步增强类型规范、约束。

代码示例:

1
2
3
4
5
6
7
8
9
$foo = 'foo';
$bar = ['a', 'b', 'c'];

$res = array_merge($foo, $bar); // NULL
$res = array_merge(null, $bar); // NULL

// 最佳实践:如不确定是否数组,则强制类型转换
$res = array_merge((array)$foo, (array)$bar); // ['foo', 'a', 'b', 'c']
$res = array_merge((array)null, (array)$bar); // ['a', 'b', 'c']

array_merge+的区别

二者相同作用:合并一个或多个数组,将一个数组附件到前一个数组之后。
区别:键名相同时,如何处理?(后者覆盖前者、保留前者、后者追加,一共有着3种情形)

  • array_merge:键名相同,字符串键名时后者覆盖前者,数字键则追加至尾部。
  • +:相同键名时,仅保留第一个数组中元素,后者重复键值忽略(不区分键类型是数值or字符串)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 数字索引的例子
// +操作符,重复数字索引:后者重复元素忽略,例子中3=>a3和3=>b2的索引值重复
$array1 = array(0 => 'zero_a', 2 => 'two_a', 3 => 'three_a');
$array2 = array(1 => 'one_b', 3 => 'three_b', 4 => 'four_b');
$result = $array1 + $array2;
var_export($result);
/* 输出结果为:
array (
0 => 'zero_a',
2 => 'two_a',
3 => 'three_a', // 重复时,+保留前者
1 => 'one_b',
4 => 'four_b',
)
*/

$result = array_merge($array1, $array2);
var_export($result);
/* 结果为:数字索引时,array_merge会重建索引,重复索引键值追加
array (
0 => 'zero_a',
1 => 'two_a',
2 => 'three_a',
3 => 'one_b',
4 => 'three_b', // 重复时,array_merge追加,并重建索引
5 => 'four_b',
)
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 索引数组,索引键为字符串
$array1 = array('a' => 'zero_a', 'c' => 'two_a', 'd' => 'three_a');
$array2 = array('b' => 'one_b', 'd' => 'three_b', 'e' => 'four_b');
$result = $array1 + $array2;
var_export($result);
/*
array (
'a' => 'zero_a',
'c' => 'two_a',
'd' => 'three_a', // 重复时,+保留前者
'b' => 'one_b',
'e' => 'four_b',
)
*/

$result = array_merge($array1, $array2);
var_export($result);
/* 二者执行结果一致,均为:
array (
'a' => 'zero_a',
'c' => 'two_a',
'd' => 'three_b', // 重复时,array_merge后者覆盖前者
'b' => 'one_b',
'e' => 'four_b',
)
*/

参考阅读:function.array-merge

索引数组的索引值

  • 索引可以非连续,非连续索引的影响:json_encode()返回结果数组 or 对象
1
2
3
4
5
6
7
8
9
$foo = ['a', 'b', 'c'];
unset($foo[1]);
var_export($foo);
/*
array (
0 => 'a',
2 => 'c',
)
*/
  • 如何重建数组索引,恢复连续自增索引值
    • array_values()
    • array_slice($foo, 0)
    • 二者效率是否有差异?原理有和区别?
1
2
3
4
5
6
7
8
9
10
11
12
$foo = ['a', 'b', 'c'];
unset($foo[1]);
$bar = array_values($foo); // 方式1
$bar = array_slice($foo, 0); // 方式2

// 两种形式的结果均为
/*
array (
0 => 'a',
1 => 'c',
)
*/

json_encode()返回JSON数组还是对象

如果 PHP 服务向前端或 Java/Golang 等强类型语言服务提供接口时,经常面临吐槽。
返回数据字段有时是数组,有时却是对象,对强类型语言而言就是灾难。

首先,搞清JSON 中数组Array、对象 Object 的各自定义(详见:json.org):

  • Array:值的有序集合,以[开始,以]结束
  • Object:无序的名称/值对的集合,以{开始,以}结束

PHP 中常用json_encode ( mixed $value [, int $options = 0 [, int $depth = 512 ]] ) : string函数进行特定类型的序列化输出。第二个参数options传递不同JSON 常量来控制序列化输出结果。

1
//

json_encode()总结:

  • 缺省第二参数时
    • 空数组时,json_encode()后输出数组 Array
    • 索引数组(键为数字),且索引键连续json_encode()后输出数组 Array
    • 索引数组(键为数字),但索引键非连续json_encode()后输出对象 Object
    • 关联数组(键为字符串),json_encode()后输出对象 Object
  • 如需强制结果返回对象
    • json_encode($value, JSON_FORCE_OBJECT),将结果强制返回对象
  • 如需强制结果返回数组
    • 索引数组的结果强制返回数组,重建索引保证索引连续,则json_encode后结果为 JSON 数组
    • 关联数组,结果仅返回数组则丢失 key 信息,需根据数据结构明确是否合理,如坚持需要可通过array_values()仅取值、忽略键信息

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 空数组,返回空数组
$foo = [];
var_dump(json_encode($foo)); // string(2) "[]"

// 索引连续,返回数组;如需对象,则使用JSON_FORCE_OBJECT参数
$foo = ['a', 'b', 'c'];
var_dump(json_encode($foo)); // string(13) "["a","b","c"]"
var_dump(json_encode($foo, JSON_FORCE_OBJECT)); // string(25) "{"0":"a","1":"b","2":"c"}"

// 索引非连续,返回对象
unset($foo[1]);
var_dump(json_encode($foo)); // string(17) "{"0":"a","2":"c"}"

// 关联数组必然返回对象
$foo = ['a' => 'aa', 'b' => 'bb', 'c' => 'cc'];
var_dump(json_encode($foo)); // string(28) "{"a":"aa","b":"bb","c":"cc"}"

// 关联数组如必须返回数组,则丢失 key 信息
var_dump(json_encode(array_values($foo))); // string(16) "["aa","bb","cc"]"

array($foo)(array)$foo的区别

  • array()是数组声明,和[]等价
  • (array)是强制类型转换

在非空变量上,二者作用相当;应特别注意,当$foo=null时,二者区别:

1
2
3
$in = null;
$foo = array($in); // 仅包含一个null的数组,[0 => null]
$bar = (array)$in; // 强制类型转换,空数组,[]

全角、半角字符

在进行字符替换时,常用来将多个空格处理成单个、或过滤空格。
但在全角状态下空格的识别,较为少见,但是比较容易忽略的。

匹配时,容易忽略的「全角空格」

1
2
3
4
5
6
7
8
9
10
// 预期:替换所有空格
$foo = "你 好,世 界 ";
$res = preg_replace('/( |\\n|\\r|\s)+/', '', $foo);
// string(15) "你好,世界"
// 成功处理,将多个空格替换为单个

$foo = "你 好,世 界  ";
$res = preg_replace('/( |\\n|\\r|\s)+/', '', $foo);
// string(27) "你 好,世 界  "
// 未能处理,无法匹配和替换「全角空格」

有问题的正则遇到全角符号

  • 正则中[]表示多个可选的字符,但只允许单个字符,全角空格为三字符(\xE3\x80\x80),则会按三个分别分别匹配、替换
  • 这样就会误替换部分中文中的字符为空,导致乱码
  • preg中u(PCRE_UTF8)修饰符,可支持utf8编码处理,从而支持多字节匹配问题
  • PS:虽然preg中的u修饰符可解决上述case,但应更加准确适用正则及其对应含义。

正则中[]的概念:[abc] A single character of: a, b or c

1
2
3
4
5
6
7
8
9
// 注意,原始字符串中包含「全角空格」,正则中[]内也有此「全角空格」
$foo = "  一个、两个,三四个。";
$res = preg_replace('/([ \s]|\\n|\\r)+/', '', $foo);
// 结果为:string(25) "�个�两个,三四个�"

// utf-8编码下的匹配
$foo = "  一个、两个,三四个。";
$res = preg_replace('/([ \s]|\\n|\\r)+/u', '', $foo);
// string(30) "一个、两个,三四个。"

字符编码

详见另一篇文章:PHP中编码检测