Skip to content

Spaceship 运算符 <=>

概述

Spaceship 运算符 <=>(也称"飞船运算符"或"三路比较运算符")是 PHP 7.0 引入的组合比较运算符。它将两个操作数进行比较,返回 -101,分别表示小于、等于和大于的关系。这使得排序和比较逻辑的编写变得极其简洁。

版本要求

Spaceship 运算符 <=>PHP 7.0 起可用。

基础概念

返回值规则

比较结果返回值
$a < $b-1(负数)
$a == $b0
$a > $b1(正数)

核心优势

在 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

<=> 返回精确值

<=> 始终返回 -101(整数类型),不会返回其他负数或正数。因此可以安全地在 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)

注意事项

  • 返回值严格为 -101:不会返回其他数值
  • 遵循 PHP 的比较规则:字符串与数字比较时的类型转换规则与 ==/</> 一致
  • 降序只需反转操作数顺序$b <=> $a 而非 ($a <=> $b) * -1
  • 多维排序使用 ?: 链式连接$a['x'] <=> $b['x'] ?: $a['y'] <=> $b['y']
  • PHP 8.0+ 字符串数字比较变更:影响 <=> 的行为

最佳实践

  1. 排序回调优先使用 <=>:比 if/elseif/else 模式更简洁
  2. 多维排序用 ?: 组合:一个表达式处理所有排序维度
  3. 通用排序器封装:将排序逻辑封装为可复用的排序器类
  4. 降序直接反转操作数$b <=> $a-$cmp 更清晰
  5. 使用 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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 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');

参考链接