必须用 usort()(或 uasort()/uksort())配合匿名函数排序,因 sort() 等不支持回调;匿名函数需返回整数、正确处理缺失键与类型、避免 I/O;大数据量需预提取字段或改用 array_multisort()。

用 usort() 传匿名函数排序数组
PHP 数组排序不支持直接把匿名函数塞进 sort() 或 asort(),必须用 usort()(或 uasort()、uksort())这类“用户自定义比较”的函数。它接收两个参数:待排序数组 + 一个可调用的比较逻辑——匿名函数正好符合这个要求。
常见错误是写成 sort($arr, function($a,$b){...}),这会报错:Warning: sort() expects parameter 2 to be int, object given —— 因为 sort() 第二个参数只接受 SORT_XXX 常量,不是回调。
-
usort()修改原数组并返回布尔值;uasort()保留键名;按需选 - 匿名函数必须返回整数:
0(相等)、>0($a 应排在 $b 后)、($a 应排在 $b 前) - 别在函数里用
return $a $b就完事——如果字段可能为null或字符串数字混排,会隐式转类型,结果出人意料
按对象属性排序时怎么写闭包捕获
要按对象数组的某个属性排序(比如 $users 按 created_at 时间戳),匿名函数需要访问外部变量或上下文。PHP 中用 use 显式捕获,不能靠自动作用域闭包。
典型翻车点:写成 function($a, $b) { return $a->score $b->score; } 看似没问题,但一旦想加条件(比如“只对 status=1 的用户排序”,或动态传入字段名),没 use 就束手无策。
立即学习“PHP免费学习笔记(深入)”;
- 字段名动态化:写成
function($a, $b) use ($field) { return $a->$field $b->$field; },然后usort($users, ...) - 带过滤逻辑:比如优先排
is_pinned === true的项,use ($pinnedFirst)传布尔开关,函数内做分支判断 -
use默认按值捕获;如需修改外部变量,得加&(但排序函数里几乎用不到)
usort() 的性能和稳定性风险
匿名函数本身没性能问题,但 usort() 是 PHP 内部快排实现,最坏情况 O(n²),且不稳定(相同键值的相对顺序可能改变)。如果数组很大(>10k 元素)或排序逻辑复杂(比如每次比较都查数据库或解 JSON),就会卡住。
更隐蔽的问题是:PHP 7.4+ 支持箭头函数,但 usort() 不接受箭头函数作为回调,因为箭头函数无法指定参数类型,且 usort() 底层要求回调有明确的双参数签名——写 fn($a,$b) => $a['id'] $b['id'] 会报 Fatal error: Uncaught ValueError: usort(): Argument #2 ($callback) must be a valid callback。
- 大数据量时,先用
array_map()提前提取排序字段(如created_at),生成索引数组再排序,比反复取属性快 - 需要稳定排序?自己实现归并排序(小数据够用),或改用
array_multisort()配合预提取字段 - 别在匿名函数里做 I/O、sleep、或调用未声明的函数——错误不会立刻暴露,而是在
usort()执行中崩
多维数组按子键排序的坑
比如 $data = [['price'=>199], ['price'=>99]],想按 price 升序,很多人直接写 usort($data, fn($a,$b) => $a['price'] $b['price'])。看似 OK,但只要某条数据缺 price 键,就会触发 Notice: Undefined index: price,整个排序中断或结果错乱。
这不是语法问题,是健壮性缺失。PHP 不会在比较函数里帮你兜底,它把 undefined index 当 null,而 null 99 是 -1,导致缺失字段的项被排到最前——完全违背直觉。
- 统一兜底:用
$a['price'] ?? 0或$a['price'] ?? PHP_INT_MAX(取决于你想让它排前还是排后) - 类型安全:如果
price应该是数字,强制转换(float)($a['price'] ?? 0),避免字符串"100"和数字99比较出错 - 嵌套更深?比如
$a['meta']['score'],别链式写$a['meta']['score'] ?? 0——PHP 8.0+ 支持空合并链$a['meta']['score'] ?? 0,但低版本必须用($a['meta']['score'] ?? null) ?? 0或isset($a['meta']['score']) ? $a['meta']['score'] : 0
真正麻烦的从来不是写一行匿名函数,而是想清楚字段是否存在、类型是否一致、相等时要不要保序、以及出错时你愿不愿意让整个列表排序失败。



















