Spaceship 运算符 <=>
概述
Spaceship 运算符 <=>(也称"飞船运算符"或"三路比较运算符")是 PHP 7.0 引入的组合比较运算符。它将两个操作数进行比较,返回 -1、0 或 1,分别表示小于、等于和大于的关系。这使得排序和比较逻辑的编写变得极其简洁。
版本要求
Spaceship 运算符 <=> 从 PHP 7.0 起可用。
基础概念
返回值规则
| 比较结果 | 返回值 |
|---|---|
$a < $b | -1(负数) |
$a == $b | 0 |
$a > $b | 1(正数) |
核心优势
在 PHP 7.0 之前,usort 等排序函数需要编写冗长的比较逻辑:
php
// PHP 5.x 写法
usort($array, function ($a, $b) {
if ($a === $b) return 0;
return $a < $b ? -1 : 1;
});
// PHP 7.0+ 写法
usort($array, fn($a, $b) => $a <=> $b);语法与代码示例
基本比较
php
<?php
declare(strict_types=1);
// 整数比较
var_dump(1 <=> 1); // int(0) —— 相等
var_dump(1 <=> 2); // int(-1) —— 小于
var_dump(2 <=> 1); // int(1) —— 大于
// 浮点数比较
var_dump(1.5 <=> 1.5); // int(0)
var_dump(1.5 <=> 2.0); // int(-1)
var_dump(2.5 <=> 1.0); // int(1)
// 字符串比较
var_dump('a' <=> 'a'); // int(0)
var_dump('a' <=> 'b'); // int(-1)
var_dump('b' <=> 'a'); // int(1)
// 混合类型比较
var_dump(1 <=> '1'); // int(0) —— 松散比较
var_dump(1 <=> 1.5); // int(-1)多维度排序
php
<?php
declare(strict_types=1);
// 多维排序:先按年龄,再按姓名
$users = [
['name' => 'Charlie', 'age' => 25],
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Diana', 'age' => 30],
['name' => 'Eve', 'age' => 20],
];
// 先按 age 升序,age 相同按 name 升序
usort($users, function ($a, $b) {
return $a['age'] <=> $b['age']
?: $a['name'] <=> $b['name'];
});
print_r($users);
// Eve(20), Bob(25), Charlie(25), Alice(30), Diana(30)
// 三维排序示例:按 score 降序,time 升序,name 升序
$scores = [
['name' => 'Alice', 'score' => 95, 'time' => 120],
['name' => 'Bob', 'score' => 95, 'time' => 110],
['name' => 'Charlie', 'score' => 88, 'time' => 100],
];
usort($scores, fn($a, $b) =>
$b['score'] <=> $a['score'] // score 降序(注意顺序反转)
?: $a['time'] <=> $b['time'] // time 升序
?: $a['name'] <=> $b['name'] // name 升序
);
print_r($scores);
// Bob(95, 110), Alice(95, 120), Charlie(88, 100)排序方向控制
php
<?php
declare(strict_types=1);
// 升序:$a <=> $b
// 降序:$b <=> $a
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];
// 升序
usort($numbers, fn($a, $b) => $a <=> $b);
print_r($numbers); // [1, 1, 2, 3, 4, 5, 6, 9]
// 降序
usort($numbers, fn($a, $b) => $b <=> $a);
print_r($numbers); // [9, 6, 5, 4, 3, 2, 1, 1]
// 通用排序辅助函数
function createComparator(string $field, string $direction = 'asc'): callable
{
return fn($a, $b) => $direction === 'desc'
? $b[$field] <=> $a[$field]
: $a[$field] <=> $b[$field];
}
$people = [
['name' => 'Zoe', 'score' => 80],
['name' => 'Amy', 'score' => 95],
['name' => 'Bob', 'score' => 90],
];
usort($people, createComparator('score', 'desc'));
print_r($people); // Amy(95), Bob(90), Zoe(80)详细说明
与对象比较的结合
php
<?php
declare(strict_types=1);
class Product
{
public function __construct(
public string $name,
public float $price,
public int $stock
) {}
}
$products = [
new Product('Widget', 9.99, 100),
new Product('Gadget', 29.99, 50),
new Product('Gizmo', 19.99, 75),
new Product('Doohickey', 9.99, 200),
];
// 多维度对象排序:先按 price 升序,再按 stock 降序
usort($products, fn(Product $a, Product $b) =>
$a->price <=> $b->price ?: $b->stock <=> $a->stock
);
foreach ($products as $p) {
echo "{$p->name}: \${$p->price} (stock: {$p->stock})\n";
}
// Widget: $9.99 (stock: 200)
// Doohickey: $9.99 (stock: 100)
// Gizmo: $19.99 (stock: 75)
// Gadget: $29.99 (stock: 50)实现可比较接口
php
<?php
declare(strict_types=1);
class Version implements Comparable
{
public function __construct(
public int $major,
public int $minor,
public int $patch = 0
) {}
public function compareTo(Version $other): int
{
return $this->major <=> $other->major
?: $this->minor <=> $other->minor
?: $this->patch <=> $other->patch;
}
public function __toString(): string
{
return "{$this->major}.{$this->minor}.{$this->patch}";
}
}
$versions = [
new Version(1, 0, 0),
new Version(2, 1, 0),
new Version(1, 1, 1),
new Version(2, 0, 5),
new Version(1, 0, 9),
];
usort($versions, fn(Version $a, Version $b) => $a->compareTo($b));
foreach ($versions as $v) {
echo $v . "\n";
}
// 1.0.0, 1.0.9, 1.1.1, 2.0.5, 2.1.0在 match 表达式中使用
php
<?php
declare(strict_types=1);
function compareValues(int $a, int $b): string
{
return match ($a <=> $b) {
-1 => "{$a} is less than {$b}",
0 => "{$a} is equal to {$b}",
1 => "{$a} is greater than {$b}",
};
}
echo compareValues(3, 5) . "\n"; // 3 is less than 5
echo compareValues(5, 5) . "\n"; // 5 is equal to 5
echo compareValues(7, 3) . "\n"; // 7 is greater than 3
// 注意:match 是严格比较,所以必须用 -1/0/1 而非 < 0/== 0/> 0
// 因为 <=> 只返回 -1、0、1<=> 返回精确值
<=> 始终返回 -1、0 或 1(整数类型),不会返回其他负数或正数。因此可以安全地在 match 表达式中使用精确匹配。
实战示例
通用数组排序工具
php
<?php
declare(strict_types=1);
class ArraySorter
{
/**
* 多字段排序
* @param array $data 要排序的数组
* @param array $fields 排序规则 [['field' => 'name', 'dir' => 'asc'], ...]
* @return array
*/
public static function sortByFields(array $data, array $fields): array
{
usort($data, function ($a, $b) use ($fields): int {
foreach ($fields as $rule) {
$field = $rule['field'];
$direction = $rule['dir'] ?? 'asc';
$aVal = is_object($a) ? $a->$field : $a[$field];
$bVal = is_object($b) ? $b->$field : $b[$field];
$cmp = $aVal <=> $bVal;
if ($cmp !== 0) {
return $direction === 'desc' ? -$cmp : $cmp;
}
}
return 0;
});
return $data;
}
}
$employees = [
['dept' => 'IT', 'name' => 'Charlie', 'salary' => 8000],
['dept' => 'IT', 'name' => 'Alice', 'salary' => 10000],
['dept' => 'HR', 'name' => 'Bob', 'salary' => 8000],
['dept' => 'IT', 'name' => 'Diana', 'salary' => 10000],
];
// 按 dept 升序,salary 降序,name 升序
$sorted = ArraySorter::sortByFields($employees, [
['field' => 'dept', 'dir' => 'asc'],
['field' => 'salary', 'dir' => 'desc'],
['field' => 'name', 'dir' => 'asc'],
]);
print_r($sorted);区间判断
php
<?php
declare(strict_types=1);
// 使用 spaceship 判断值是否在区间内
function clamp(int $value, int $min, int $max): int
{
// spaceship 返回 -1/0/1,配合 match 实现区间判断
return match (true) {
$value <=> $min === -1 => $min, // 值小于最小值,返回最小值
$value <=> $max === 1 => $max, // 值大于最大值,返回最大值
default => $value, // 在范围内,返回原值
};
}
echo clamp(5, 0, 10) . "\n"; // 5
echo clamp(-5, 0, 10) . "\n"; // 0
echo clamp(15, 0, 10) . "\n"; // 10
// 更简洁的写法(不需要 spaceship)
function clampSimple(int $value, int $min, int $max): int
{
return max($min, min($max, $value));
}查找最近值
php
<?php
declare(strict_types=1);
function findClosest(int $target, array $values): ?int
{
if (empty($values)) {
return null;
}
usort($values, fn($a, $b) =>
abs($a - $target) <=> abs($b - $target)
);
return $values[0];
}
$prices = [50, 75, 100, 120, 150];
echo findClosest(90, $prices) . "\n"; // 75(距离15)
echo findClosest(110, $prices) . "\n"; // 100(距离10)注意事项
- 返回值严格为
-1、0、1:不会返回其他数值 - 遵循 PHP 的比较规则:字符串与数字比较时的类型转换规则与
==/</>一致 - 降序只需反转操作数顺序:
$b <=> $a而非($a <=> $b) * -1 - 多维排序使用
?:链式连接:$a['x'] <=> $b['x'] ?: $a['y'] <=> $b['y'] - PHP 8.0+ 字符串数字比较变更:影响
<=>的行为
最佳实践
- 排序回调优先使用
<=>:比if/elseif/else模式更简洁 - 多维排序用
?:组合:一个表达式处理所有排序维度 - 通用排序器封装:将排序逻辑封装为可复用的排序器类
- 降序直接反转操作数:
$b <=> $a比-$cmp更清晰 - 使用
match处理比较结果(PHP 8.0+):比switch更简洁安全
进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');