usort() 是最常用、最可控的多列排序方式,支持任意字段、顺序和类型比较,需返回整数且逐级判断;PHP 7.4+ 可用箭头函数简化,但复杂逻辑仍需匿名函数;避免 array_multisort() 的陷阱及对象属性访问问题。

PHP usort() 配合匿名函数实现多列排序
直接用 usort() 是最常用、最可控的方式。它不依赖数组键,支持任意字段(包括嵌套属性)、任意顺序(升/降)、任意类型比较(字符串、数字、日期),且 PHP 5.3+ 均可用。
关键点在于:比较逻辑必须返回整数(、<code>0、> 0),不能只返回布尔值;多列时需逐级判断,前一列相等才比下一列。
常见错误是写成:return $a['age'] ——这会把相等情况全当 <code>$a > $b 处理,导致排序错乱。
示例:按 status 升序,再按 created_at 降序(时间戳):
立即学习“PHP免费学习笔记(深入)”;
usort($data, function($a, $b) {
// 第一优先级:status 升序
if ($a['status'] !== $b['status']) {
return $a['status'] < $b['status'] ? -1 : 1;
}
// 第二优先级:created_at 降序(注意是 b - a)
return $b['created_at'] - $a['created_at'];
});PHP 7.4+ 可用箭头函数简化写法
如果项目已上 PHP 7.4+,且逻辑简单(比如全是数字或可直接比较的字符串),可用箭头函数减少样板代码,但要注意:箭头函数不能包含多语句,所以复杂逻辑仍得回退到普通匿名函数。
适用场景有限,别为了“新”而硬套。容易踩的坑是误以为箭头函数能自动处理相等情况——它一样要显式分支。
例如仅按 score 降序 + name 升序(假设 name 是 ASCII 字符串):
usort($data, fn($a, $b) =>
$a['score'] <> $b['score'] ?: strcmp($a['name'], $b['name'])
); 是飞船操作符(spaceship operator),PHP 7 引入,对数字/字符串安全;?: 表示前为 0 时才执行后半段。
避免用 array_multisort() 的陷阱
array_multisort() 看似直观,但它要求你先提取出各列数组,且对原始数组结构敏感——比如二维数组里某行缺字段,就会触发 Notice: Undefined index,甚至因类型隐式转换导致排序错位(如字符串 "10" 排在 "2" 前面)。
更麻烦的是:它默认按「数值升序」排,想降序得额外传 SORT_DESC 参数,参数顺序和数组顺序必须严格对应,错一位就全乱。
除非你已在循环外提前提取好各列且确认数据干净,否则不建议用。真实项目中,90% 的多列排序需求用 usort() 更稳。
- 别传
array_column($data, 'price')后直接跟SORT_DESC——如果某条数据没有price,那一整列都会塌缩 - 不要混用
SORT_STRING和SORT_NUMERIC在同一调用里,容易因类型推断失败翻车 - 它不支持闭包逻辑(比如按「状态中文名」排序需先映射,
array_multisort()做不到)
对象数组排序要小心属性可见性
如果 $data 是对象数组(比如 App\User 实例),usort() 里访问私有/受保护属性会失败,报 Cannot access private property。
解决方式只有两个:要么加 public getter 方法(推荐),要么用反射(不推荐,性能差且破坏封装)。
错误写法:$a->name(name 是 private);正确写法:$a->getName() 或 $a->name(仅当 public)。
另外,对象比较时别漏掉 null 判断——比如某个对象的 getCreatedAt() 返回 null,直接减法会出 Warning: A non-numeric value encountered。
稳妥做法是统一转时间戳并兜底:
usort($users, function($a, $b) {
$t1 = $a->getCreatedAt() ? $a->getCreatedAt()->getTimestamp() : 0;
$t2 = $b->getCreatedAt() ? $b->getCreatedAt()->getTimestamp() : 0;
return $t2 <=> $t1; // 降序
});多列排序真正难的不是语法,而是每列的「空值策略」和「类型归一化」——比如 null 该排最前还是最后?字符串大小写要不要忽略?时间字段是字符串还是 DateTime 对象?这些细节没对齐,排序结果看着“差不多”,实则线上查半天 bug 才发现是某列空值被当成 0 比了。



















