PHPの宇宙船演算子について

採用はこちら

PHPの宇宙船演算子とは、2つの値を比較して、大小関係を整数で返す比較演算子です。

記号は次のように書きます。

<=>

たとえば、次のように使います。

$a <=> $b

宇宙船演算子は、左辺と右辺を比較して、次のような結果を返します。

比較結果返り値
左辺が右辺より小さい-1
左辺と右辺が等しい0
左辺が右辺より大きい1

たとえば、次のようになります。

echo 1 <=> 2; // -1
echo 2 <=> 2; // 0
echo 3 <=> 2; // 1

つまり、宇宙船演算子は「小さい」「等しい」「大きい」を1つの演算子で判定できる便利な機能です。

厳密には、PHPの仕様上は「左辺が小さければ0未満、等しければ0、大きければ0より大きい整数」を返します。

ただし、通常の使い方では -101 を返すものとして理解して問題ありません。

目次

宇宙船演算子の基本的な使い方

数値を比較する

宇宙船演算子は、まず数値の比較で考えると分かりやすいです。

echo 10 <=> 20; // -1
echo 10 <=> 10; // 0
echo 20 <=> 10; // 1

このコードでは、次のように比較されています。

10 < 20 なので -1
10 = 10 なので 0
20 > 10 なので 1

通常、2つの値を比較して結果を返す場合、次のように if 文で書くことがあります。

if ($a < $b) {
    return -1;
} elseif ($a > $b) {
    return 1;
} else {
    return 0;
}

しかし、宇宙船演算子を使えば、これを1行で書けます。

return $a <=> $b;

コードが短くなり、比較している意図も分かりやすくなります。

宇宙船演算子はソート処理でよく使われる

usort() と相性がよい

宇宙船演算子がよく使われる場面は、配列の並び替えです。

PHPには、独自のルールで配列を並び替える usort() という関数があります。

たとえば、数値の配列を昇順に並び替える場合は、次のように書けます。

$numbers = [3, 1, 4, 2];

usort($numbers, function ($a, $b) {
    return $a <=> $b;
});

print_r($numbers);

実行結果は次のようになります。

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
)

usort() の比較関数では、比較結果として「0未満」「0」「0より大きい整数」を返します。

宇宙船演算子はその形式に合った値を返すため、usort() と非常に相性がよいです。

昇順と降順の書き方

昇順に並び替える

昇順、つまり小さい順に並び替える場合は、次のように書きます。

usort($numbers, function ($a, $b) {
    return $a <=> $b;
});

PHP 7.4以降であれば、アロー関数を使ってさらに短く書けます。

usort($numbers, fn($a, $b) => $a <=> $b);

$a <=> $b と書くと、小さい値が前に来ます。

$numbers = [3, 1, 4, 2];

usort($numbers, fn($a, $b) => $a <=> $b);

print_r($numbers);

結果は次のとおりです。

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
)

降順に並び替える

降順、つまり大きい順に並び替える場合は、左右を逆にします。

usort($numbers, function ($a, $b) {
    return $b <=> $a;
});

アロー関数で書くと、次のようになります。

usort($numbers, fn($a, $b) => $b <=> $a);

実行例です。

$numbers = [3, 1, 4, 2];

usort($numbers, fn($a, $b) => $b <=> $a);

print_r($numbers);

結果は次のようになります。

Array
(
    [0] => 4
    [1] => 3
    [2] => 2
    [3] => 1
)

覚え方はシンプルです。

$a <=> $b // 昇順
$b <=> $a // 降順

文字列の比較にも使える

通常の文字列を比較する

宇宙船演算子は、数値だけでなく文字列の比較にも使えます。

echo 'apple' <=> 'banana'; // -1
echo 'apple' <=> 'apple';  // 0
echo 'orange' <=> 'banana'; // 1

文字列の場合は、辞書順に近いルールで比較されます。

たとえば、名前の配列を並び替える場合は、次のように書けます。

$names = ['Taro', 'Hanako', 'Jiro'];

usort($names, function ($a, $b) {
    return $a <=> $b;
});

print_r($names);

実行結果です。

Array
(
    [0] => Hanako
    [1] => Jiro
    [2] => Taro
)

数値文字列の比較には注意する

文字列比較で注意したいのが、数値として解釈できる文字列です。

たとえば、次のような比較があります。

echo '10' <=> '2';

一見すると、文字列として比較されて '10' のほうが小さくなりそうに見えるかもしれません。

しかし、PHPでは '10''2' のような数値文字列は、数値として比較されます。

そのため、次の比較と同じように扱われます。

echo 10 <=> 2; // 1

つまり、結果は 1 です。

echo '10' <=> '2'; // 1

純粋な文字列として比較したい場合は、strcmp() のような文字列比較用の関数を使うことも検討しましょう。

連想配列を並び替える

特定のキーを基準にソートする

宇宙船演算子は、連想配列の並び替えでもよく使われます。

たとえば、ユーザー一覧を年齢順に並び替える例です。

$users = [
    ['name' => 'Taro', 'age' => 30],
    ['name' => 'Hanako', 'age' => 25],
    ['name' => 'Jiro', 'age' => 35],
];

usort($users, function ($a, $b) {
    return $a['age'] <=> $b['age'];
});

print_r($users);

実行結果は次のようになります。

Array
(
    [0] => Array
        (
            [name] => Hanako
            [age] => 25
        )

    [1] => Array
        (
            [name] => Taro
            [age] => 30
        )

    [2] => Array
        (
            [name] => Jiro
            [age] => 35
        )
)

この例では、age の値を比較しています。

return $a['age'] <=> $b['age'];

これにより、年齢が小さい順に並び替えられます。

価格順に並び替える

商品一覧を価格順に並び替える場合も、同じ考え方です。

$products = [
    ['name' => 'Product A', 'price' => 3000],
    ['name' => 'Product B', 'price' => 1000],
    ['name' => 'Product C', 'price' => 2000],
];

usort($products, function ($a, $b) {
    return $a['price'] <=> $b['price'];
});

print_r($products);

価格が安い順に並び替えられます。

高い順にしたい場合は、左右を逆にします。

usort($products, function ($a, $b) {
    return $b['price'] <=> $a['price'];
});

オブジェクトの並び替えにも使える

プロパティを比較してソートする

宇宙船演算子は、オブジェクトの配列を並び替えるときにも使えます。

たとえば、User オブジェクトを年齢順に並び替える例です。

class User
{
    public string $name;
    public int $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

$users = [
    new User('Taro', 30),
    new User('Hanako', 25),
    new User('Jiro', 35),
];

usort($users, function ($a, $b) {
    return $a->age <=> $b->age;
});

foreach ($users as $user) {
    echo $user->name . ': ' . $user->age . PHP_EOL;
}

実行結果です。

Hanako: 25
Taro: 30
Jiro: 35

このコードでは、オブジェクトそのものを比較しているのではなく、age プロパティを比較しています。

return $a->age <=> $b->age;

なお、上記のような型付きプロパティは PHP 7.4以降で使えます。

宇宙船演算子自体は PHP 7.0 から使えますが、PHP 7.0〜7.3 も対象にする場合は、型付きプロパティを使わずに書く必要があります。

複数条件で並び替える

年齢順、同じ年齢なら名前順にする

宇宙船演算子は、複数条件のソートにも便利です。

たとえば、次のようなユーザー一覧があるとします。

$users = [
    ['name' => 'Taro', 'age' => 30],
    ['name' => 'Hanako', 'age' => 25],
    ['name' => 'Jiro', 'age' => 30],
    ['name' => 'Akira', 'age' => 25],
];

この配列を、まず年齢順に並び替え、年齢が同じ場合は名前順に並び替えるには、次のように書けます。

usort($users, function ($a, $b) {
    return ($a['age'] <=> $b['age'])
        ?: ($a['name'] <=> $b['name']);
});

このコードでは、まず次の比較が行われます。

$a['age'] <=> $b['age']

年齢が異なる場合は、ここで -1 または 1 が返ります。

年齢が同じ場合は 0 が返ります。

PHPでは 0 は false として扱われるため、次の比較に進みます。

$a['name'] <=> $b['name']

つまり、年齢が同じユーザー同士だけ、名前で比較されます。

読みやすさを重視した書き方

条件が増えたり、処理の意味を分かりやすくしたい場合は、変数に分けて書く方法もあります。

usort($users, function ($a, $b) {
    $ageCompare = $a['age'] <=> $b['age'];

    if ($ageCompare !== 0) {
        return $ageCompare;
    }

    return $a['name'] <=> $b['name'];
});

処理は少し長くなりますが、何を比較しているのかが分かりやすくなります。

実務では、条件が2つ程度なら次のような短い書き方でも十分読みやすいです。

return ($a['age'] <=> $b['age'])
    ?: ($a['name'] <=> $b['name']);

一方で、条件が3つ以上ある場合や、比較条件が複雑な場合は、変数に分けたほうが保守しやすくなります。

配列を使って複数条件を比較する方法

同じ構造の配列ならまとめて比較できる

複数条件のソートでは、比較したい値を配列にまとめる書き方もできます。

usort($users, function ($a, $b) {
    return [$a['age'], $a['name']] <=> [$b['age'], $b['name']];
});

この書き方では、まず age が比較され、age が同じ場合は name が比較されます。

つまり、次のコードと近い意味になります。

return ($a['age'] <=> $b['age'])
    ?: ($a['name'] <=> $b['name']);

複数条件をコンパクトに書けるため、実務でも便利です。

連想配列そのものを比較するのは避けたほうがよい

PHPでは、配列同士を宇宙船演算子で比較することもできます。

var_dump([1, 2] <=> [1, 3]); // -1
var_dump([1, 2] <=> [1, 2]); // 0
var_dump([2, 1] <=> [1, 9]); // 1

ただし、連想配列そのものを比較すると、キー構造や要素数なども比較ルールに影響します。

そのため、実務では次のように配列そのものを比較する書き方はあまりおすすめしません。

return $a <=> $b;

比較したい値が明確な場合は、次のようにキーを指定しましょう。

return $a['age'] <=> $b['age'];

または、複数条件なら次のように書くと分かりやすいです。

return [$a['age'], $a['name']] <=> [$b['age'], $b['name']];

日付の比較に使う場合

Y-m-d 形式なら文字列のまま比較できる

日付の比較にも、宇宙船演算子を使えます。

たとえば、日付が Y-m-d 形式でそろっている場合です。

$posts = [
    ['title' => 'Post A', 'published_at' => '2024-05-01'],
    ['title' => 'Post B', 'published_at' => '2024-04-15'],
    ['title' => 'Post C', 'published_at' => '2024-06-10'],
];

usort($posts, function ($a, $b) {
    return $a['published_at'] <=> $b['published_at'];
});

2024-04-152024-05-012024-06-10 のように、年・月・日がゼロ埋めされた形式でそろっていれば、文字列の並び順と日付の順番が一致します。

そのため、上記のコードで古い日付順に並び替えられます。

新しい日付順にしたい場合は、左右を逆にします。

usort($posts, function ($a, $b) {
    return $b['published_at'] <=> $a['published_at'];
});

日付形式がバラバラな場合は変換してから比較する

日付形式がそろっていない場合は、文字列のまま比較すると正しく並ばない可能性があります。

たとえば、次のような日付が混ざっている場合です。

2024/5/1
2024-05-01
May 1, 2024

このような場合は、strtotime() でタイムスタンプに変換してから比較するとよいでしょう。

usort($posts, function ($a, $b) {
    return strtotime($b['published_at']) <=> strtotime($a['published_at']);
});

ただし、strtotime() は変換に失敗すると false を返します。

入力値が不確かな場合は、日付の形式を事前にそろえるか、DateTimeImmutable を使って安全に扱うのがおすすめです。

strcmp() との違い

宇宙船演算子は汎用的な比較に使える

文字列比較では、strcmp() という関数もよく使われます。

strcmp($a, $b)

strcmp() も、比較結果として0未満、0、0より大きい整数を返します。

strcmp('apple', 'banana'); // 0未満
strcmp('apple', 'apple');  // 0
strcmp('orange', 'banana'); // 0より大きい

宇宙船演算子との違いは、strcmp() が文字列比較専用であるのに対して、宇宙船演算子は数値や文字列、配列などさまざまな値の比較に使える点です。

$a <=> $b

ただし、宇宙船演算子はPHPの通常の比較ルールに従います。

そのため、数値文字列は数値として比較されることがあります。

echo '10' <=> '2'; // 1

純粋に文字列として比較したい場合は、strcmp() を使うほうが意図が明確です。

echo strcmp('10', '2');

自然順ソートには strnatcmp() が向いている

ファイル名のような文字列を並び替える場合、通常の文字列比較では人間の感覚と違う順番になることがあります。

たとえば、次のような文字列です。

file1
file2
file10

通常の文字列比較では、次のような順番になる場合があります。

file1
file10
file2

人間の感覚に近い自然な順番にしたい場合は、strnatcmp() を使うとよいでしょう。

$files = ['file1', 'file10', 'file2'];

usort($files, 'strnatcmp');

print_r($files);

結果は次のようになります。

Array
(
    [0] => file1
    [1] => file2
    [2] => file10
)

===== との違い

宇宙船演算子は等価判定ではなく大小比較を行う

宇宙船演算子は、===== と混同しないようにしましょう。

それぞれの役割は異なります。

演算子用途
==型変換を含めて、値が等しいかを比較する
===値と型が同じかを比較する
<=>左辺と右辺の大小関係を整数で返す

たとえば、== は値が等しいかどうかを判定します。

var_dump(10 == '10'); // true

=== は、値だけでなく型も同じかどうかを判定します。

var_dump(10 === '10'); // false

一方、宇宙船演算子は大小関係を返します。

echo 10 <=> 20; // -1
echo 10 <=> 10; // 0
echo 20 <=> 10; // 1

等しいかどうかだけを知りたい場合は ===== を使い、並び替えなどで大小関係を知りたい場合は <=> を使う、という使い分けになります。

型が違う値を比較するときの注意点

PHPの通常の比較ルールに従う

宇宙船演算子は、PHPの通常の比較ルールに従います。

そのため、型が違う値を比較すると、型変換の影響を受けることがあります。

たとえば、数値と数値文字列を比較する場合です。

var_dump(10 <=> '10'); // 0

'10' は数値として解釈できる文字列なので、数値の 10 と同じ値として比較されます。

このように、数値、数値文字列、通常の文字列、nullbool などが混ざると、意図しない比較結果になる可能性があります。

実務では、比較前に型をそろえるのがおすすめです。

価格を数値として比較したい場合は、次のように書けます。

return (int)$a['price'] <=> (int)$b['price'];

小数を含む価格なら、float に変換します。

return (float)$a['price'] <=> (float)$b['price'];

商品コードなどを文字列として比較したい場合は、文字列にそろえます。

return (string)$a['code'] <=> (string)$b['code'];

PHPのバージョン差にも注意する

PHP 7系とPHP 8系では、数値と文字列の比較ルールが一部変わっています。

そのため、古いPHP環境を対象にする場合は、型が混ざった比較に特に注意が必要です。

安全に書くなら、宇宙船演算子に渡す前に、比較したい型へ明示的に変換しておきましょう。

return (int)$a['score'] <=> (int)$b['score'];

このように書くことで、PHPの暗黙的な型変換に依存しにくくなります。

null を含む値を比較するときの注意点

null を先頭にするか最後にするかを明示する

比較対象に null が含まれる場合は、注意が必要です。

たとえば、次のようなユーザー一覧があるとします。

$users = [
    ['name' => 'Taro', 'score' => 80],
    ['name' => 'Hanako', 'score' => null],
    ['name' => 'Jiro', 'score' => 90],
];

このとき、単純に次のように書くこともできます。

usort($users, function ($a, $b) {
    return $a['score'] <=> $b['score'];
});

しかし、null をどこに並べたいのかが分かりにくくなります。

実務では、null を先頭にするのか、最後にするのかを明示したほうが安全です。

たとえば、null を最後にしたい場合は、次のように書けます。

usort($users, function ($a, $b) {
    if ($a['score'] === null && $b['score'] === null) {
        return 0;
    }

    if ($a['score'] === null) {
        return 1;
    }

    if ($b['score'] === null) {
        return -1;
    }

    return $a['score'] <=> $b['score'];
});

このコードでは、scorenull のデータを後ろに回しています。

null を先頭にしたい場合は、return 1return -1 の部分を逆にします。

実務でよく使う宇宙船演算子のパターン

数値を昇順にする

usort($numbers, fn($a, $b) => $a <=> $b);

小さい順に並びます。

数値を降順にする

usort($numbers, fn($a, $b) => $b <=> $a);

大きい順に並びます。

価格が安い順にする

usort($products, function ($a, $b) {
    return $a['price'] <=> $b['price'];
});

価格が高い順にする

usort($products, function ($a, $b) {
    return $b['price'] <=> $a['price'];
});

投稿日が新しい順にする

usort($posts, function ($a, $b) {
    return strtotime($b['published_at']) <=> strtotime($a['published_at']);
});

優先度が高い順、同じなら作成日が古い順にする

usort($tasks, function ($a, $b) {
    return ($b['priority'] <=> $a['priority'])
        ?: (strtotime($a['created_at']) <=> strtotime($b['created_at']));
});

年齢順、同じなら名前順にする

usort($users, function ($a, $b) {
    return ($a['age'] <=> $b['age'])
        ?: ($a['name'] <=> $b['name']);
});

複数条件を配列でまとめて比較する

usort($users, function ($a, $b) {
    return [$a['age'], $a['name']] <=> [$b['age'], $b['name']];
});

Laravelで宇宙船演算子を使う場面

DBの並び替えは基本的にSQL側で行う

Laravelなどのフレームワークを使っている場合、データベースから取得するデータの並び替えは、基本的にSQL側で行います。

たとえば、年齢順にユーザーを取得する場合は、次のように書きます。

User::orderBy('age', 'asc')->get();

このようなケースでは、PHP側で宇宙船演算子を使って並び替える必要はあまりありません。

CollectionやAPIレスポンスの並び替えで使える

一方で、次のようなケースではPHP側でソートすることがあります。

  • APIから取得した配列を並び替える
  • Collectionの中身を独自ルールで並び替える
  • DBには存在しない計算値で並び替える
  • 複数のデータソースを結合したあとに並び替える
  • 表示用に一時的なソートを行う

Laravel Collectionで独自ルールの並び替えをしたい場合は、次のように書けます。

$sorted = collect($users)->sort(function ($a, $b) {
    return $a['age'] <=> $b['age'];
});

単純に特定のキーで並び替えるだけなら、sortBy() のほうが読みやすいです。

$sorted = collect($users)->sortBy('age');

降順なら sortByDesc() が使えます。

$sorted = collect($users)->sortByDesc('age');

宇宙船演算子は、単純なキー指定では表現しにくい独自ルールのソートで使うと便利です。

宇宙船演算子を使うメリット

コードを短く書ける

宇宙船演算子を使うと、比較処理を短く書けます。

通常の if 文で書くと、次のようになります。

if ($a < $b) {
    return -1;
} elseif ($a > $b) {
    return 1;
} else {
    return 0;
}

宇宙船演算子なら、次の1行で済みます。

return $a <=> $b;

ソート処理の意図が分かりやすい

usort() などの比較関数では、比較結果を整数で返す必要があります。

宇宙船演算子を使うと、比較している値が明確になります。

usort($items, fn($a, $b) => $a['sort_order'] <=> $b['sort_order']);

このコードを見るだけで、sort_order を基準に並び替えていることが分かります。

複数条件のソートを書きやすい

宇宙船演算子は、複数条件のソートとも相性がよいです。

return ($a['category'] <=> $b['category'])
    ?: ($a['price'] <=> $b['price'])
    ?: ($a['name'] <=> $b['name']);

このコードでは、次の順番で比較しています。

  1. category
  2. price
  3. name

最初の比較で差があれば、その結果を返します。

差がなければ次の条件で比較します。

宇宙船演算子を使うときの注意点

型をそろえてから比較する

宇宙船演算子は便利ですが、PHPの比較ルールに従うため、型が混ざると意図しない結果になることがあります。

特に、フォーム入力やCSV、外部APIの値は、数値に見えても文字列として入っていることがあります。

価格や点数などを数値として比較したい場合は、次のように型をそろえましょう。

return (int)$a['price'] <=> (int)$b['price'];

小数を含む場合は、次のようにします。

return (float)$a['rate'] <=> (float)$b['rate'];

null の扱いを明確にする

null が混ざる可能性がある場合は、null を先頭にするのか、最後にするのかを明示しましょう。

if ($a['score'] === null && $b['score'] === null) {
    return 0;
}

if ($a['score'] === null) {
    return 1;
}

if ($b['score'] === null) {
    return -1;
}

return $a['score'] <=> $b['score'];

このように書くことで、ソート結果が予測しやすくなります。

文字列の自然順ソートには別の関数を使う

ファイル名や商品番号のように、人間の感覚に近い順番で並べたい場合は、宇宙船演算子よりも strnatcmp() が向いていることがあります。

usort($files, 'strnatcmp');

通常の文字列比較と自然順ソートは結果が異なる場合があるため、用途に応じて使い分けましょう。

まとめ

PHPの宇宙船演算子 <=> は、2つの値を比較して、大小関係を整数で返す演算子です。

$a <=> $b

基本的な返り値は次のとおりです。

$a < $b なら -1
$a == $b なら 0
$a > $b なら 1

特に、usort() などのソート処理でよく使われます。

昇順にしたい場合は、次のように書きます。

$a <=> $b

降順にしたい場合は、左右を逆にします。

$b <=> $a

連想配列やオブジェクトの配列を並び替える場合にも便利です。

usort($users, function ($a, $b) {
    return $a['age'] <=> $b['age'];
});

複数条件のソートでは、次のように書けます。

return ($a['age'] <=> $b['age'])
    ?: ($a['name'] <=> $b['name']);

また、配列を使って複数条件をまとめることもできます。

return [$a['age'], $a['name']] <=> [$b['age'], $b['name']];

ただし、宇宙船演算子はPHPの通常の比較ルールに従うため、数値文字列、nullbool、型が混ざった値を比較する場合は注意が必要です。

実務では、比較する前に型をそろえたり、null の扱いを明示したりすると、予期しないソート結果を防ぎやすくなります。

宇宙船演算子は、比較処理を短く、分かりやすく書ける便利な演算子です。

特に配列やオブジェクトの並び替えを行う場面では、覚えておくとPHPコードをすっきり書けるようになります。

以上、PHPの宇宙船演算子についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次