PHPのnull合体演算子とは?なんでもisset()から卒業する

PHPのnull合体演算子とは? 基礎
※当サイトはアフィリエイト広告を掲載しています。

PHPには、null合体演算子という機能があります。PHP7.0で導入された演算子で、??を使い、isset()の代用として便利に使える機能です。

しかしながら、PHP3から触れてきた私にはなかなかisset()の癖が抜けず、この機能を避けてきてしまいました。同じような方もいらっしゃるのではないでしょうか?

本ページでは、null合体演算子の解説を通じ、「なんでもisset()」状態からステップアップすることを目標に書いています。

null合体演算子 ?? とは

概要

null合体演算子 (??) は、PHP 7.0 で導入された新しい演算子で、左辺の変数や値が null の場合に限り、右辺の値を返すという特性を持っています。

これにより、変数の値が null であるかどうかを簡潔にチェックし、null の場合にデフォルト値を提供することが非常に簡単になります。

$name = null;
$displayName = $name ?? "ゲスト";
echo $displayName;  // 出力: "ゲスト"

上記では意図的に$namenullを入れていますが、$nameが未定義であったとしても警告を出さないところがisset()と似ているところです。

isset()での例

先ほどの例を、isset()で表現した例を挙げます。初心者の方であれば、こんなコードでしょうか。

$name = null;

if (isset($name)) {
    $displayName = $name;
} else {
    $displayName = "ゲスト";
}

echo $displayName;  // 出力: "ゲスト"

多くの方は、以下のように書くと思います。

$name = null;
$displayName = isset($name) ? $name : "ゲスト";
echo $displayName;  // 出力: "ゲスト"

三項演算子を使うことで、そう変わらない感じで書けます。慣れてしまえば、私はこれでも見やすいと感じています。

isset()が問題になるケース

isset()を利用しての三項演算子は、ネストすると非常に見にくくなる問題があります。ネストした三項演算子のコード例を挙げます。

$name = null;
$role = null;
$location = null;

$displayName = isset($name) ? $name : (isset($role) ? $role : (isset($location) ? $location : "ゲスト"));

echo $displayName;  // 出力: "ゲスト"

このように、途端に可読性が下がってしまいます。

Lara
Lara

意味不明……。でも、三項演算子はネストさせないようにするのでこういう状況は少ないかも。

??を使った例

先ほどのコードを、??を使って書き直してみます。以下の様に、??で繋げて書くことが可能です。

$name = null;
$role = null;
$location = null;

$displayName = $name ?? $role ?? $location ?? "ゲスト";

echo $displayName;  // 出力: "ゲスト"

とても分かりやすく書くことができました。5行目の$name,$role, $locationは左から順に評価され、nullでなかったものが$displayNameに代入されます。

基本的に、代入の用途においては三項演算子を使ったisset()よりも??を使った方が簡潔に書けますね。

null合体演算子の注意点

null合体演算子??は便利でありながら、使用する際にいくつかの注意点や制限が存在します。これらを理解しておくことで、効果的かつ安全に演算子を利用することができます。

厳密にnullを評価する

??は厳密に変数がnullかどうかを評価します。

つまりは変数が0, ''(空文字), falseなどの「falsy」な値(論理型コンテキストに現れたときに偽とみなされる値)を持っている場合でも、その値が返されることを意味します。

$value = false;
echo $value ?? 'default';  // 出力: false

これに関してはisset()と同じです。忘れると意図しないコードになりますのでご注意ください。

isset()との違いを認識しておく

多くのケースではisset()の代わりに使えますが、違いをきちんと認識しておく必要があります。

isset()は変数がnullでない場合にtrueを返しますが、nullの場合はfalseを返します。つまり返される値は常にbool値です。

??は左辺の変数がnullでない場合に左辺の変数を、nullなら右辺の値を返します。

返される値が違うということは、要は??は完全な代替・上位互換ではありません。bool値が返ってきた方が都合が良い場合は、従来通りisset()でOKです。

非存在の変数の警告が出ない

これまでにも書いた通り、??は変数が存在しない場合に警告が表示されません。これがありがたくもあるのですが、逆に変数のタイプミスがあっても警告が出ないことを意味します。

警告が出ない=そのまま発見されないと思わぬバグに繋がりかねません。そういった仕様であること理解し、慎重にコードを書く&確認する必要があります。

PHPのバージョン

これまでにも書いた通り、PHP7.0から導入された機能です。したがって、それ以前のバージョンのPHPでこの演算子を使用するとエラーが発生します。

最新がPHP8.3の現在において、PHP7.0以下で動作している環境は皆無に等しいでしょうが、念頭に置く必要があります。

今ならPHP8未満で動いているシステムがあれば、アップデート提案が最優先です。私もそうですが、フリーランスエンジニアは提案力が重要。PHPのアップデート案件は定期的に発生し、そのままで動かないことが多いのでありがたいですね。

??=代入演算子

PHP 7.4では、null合体演算子がさらに進化し、??=という新しい代入演算子が追加されました。

この演算子を使用すると、変数がnullの場合に限り、右辺の値をその変数に代入することができます。

参考例

まずはこれまでに解説した、通常の??演算子を使った例です。

$variable = $variable ??  'default value';

これを、PHP7.4からの??=代入演算子を使った例に書き換えてみます。

$variable ??= 'default value';

この2つのコードで意味は同じです。要は+=??版みたいなものですね。

こちらの方が簡潔に表現できるので、PHP7.4以降で適所があれば、この方法を使っても良いでしょう。

実際のコード例: ??の活用法

さて、一通りの??の使い方を学んだところで、最後にコード例をいくつか挙げます。ご利用の際の参考にしていただければ幸いです。

チェックボックスの取得

Webフォームのチェックスボックスが未入力である場合に、デフォルト値を設定することができます。

<form method="post" action="process.php">
    <label>
        <input type="checkbox" name="accept_terms" value="yes"> 利用規約に同意する
    </label>
    <input type="submit" value="送信">
</form>
<?php
$acceptTerms = $_POST['accept_terms'] ?? 'no';

if ($acceptTerms == 'yes') {
    echo "利用規約に同意しました!";
} else {
    echo "利用規約に同意していません。";
}

チェックボックスは、チェックを入れない場合はnullになるのでこのコードが成立します(ラジオボタンもOK)。

しかしながら、<input type="text" name="username" value="">のようなテキストボックスの場合は、ユーザーの入力が無ければ''(空文字)になってしまうのでこの方法は使えません。

そちらはempry()などで判定した方が良いでしょう。

$username = !empty($_POST['username']) ? $_POST['username'] : 'guest';

設定や状態の更新

アプリケーションの設定値やオプションを配列やオブジェクトから取得する際、特定のキーが存在しない場合のデフォルト値を設定するのに有用です。

$config['timeout'] ??= 30;

関数やメソッドの引数

関数やメソッドの引数で、オプショナルな引数がnullの場合にデフォルト値を持たせたいときにも??を利用できます。

function displayMessage($message = null) {
    $text = $message ?? 'デフォルトのメッセージ';
    echo $text;
}

1行目にパラメーターの初期値として埋め込むこともできますが、長いメッセージであればこの方が可読性が良さそうです。

オブジェクトのプロパティアクセス

オブジェクトのプロパティがオプションであり、存在するかが不確実な場合があります。

こういった場合に、存在しなかった場合の初期値を代入することができます。

$profileImage = $user->profile->image ?? 'default_image.png';

自分で作ったクラスであれば、それほど便利に感じないかもしれません。

しかし例えば、他サイトのAPIで取得したJSONをデコードしたクラスであれば、何が入っているかわかりません。以下のような場合、$videoは何が入っているか分からないので、??で判定擦る手は使えそうです。

$apiUrl = 'https://example.com/api/hoge';

$header = array(
    "Content-Type: application/x-www-form-urlencoded",
    "User-Agent: Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0",
    "Referer: https://example.jp"
);

$options = array(
    'http' => array(
        'method' => "GET",
        'header' => implode("\r\n", $header),
    )
);

// コンテンツ取得
$videoContent = file_get_contents($apiUrl, false, stream_context_create($options));
$video = json_decode($videoContent);
// ↑$videoは何が入っているか保証されない

// null合体演算子を使用して、$videoのitems[0]のsnippetが存在しない場合はnullを返す
$snippet = $video->items[0]->snippet ?? null;

if (!$snippet) {
    return null;
}
// null合体演算子を使用して、サムネイルURLが存在しない場合はデフォルトを返す
$thumbnail = $snippet->thumbnails->medium->url ?? '/default_image.png';

まとめ

長年の癖というのはしみついており、私はisset()から抜け出せませんでした。しかしながら、自分で記事にまとめたおかげで、今後は自信を持って????=を使っていくことができそうです。

エンジニアはブログを書くことが大事というのは、心底本当に思います。

本記事が私以外にも役立つことがあれば、とてもうれしいです。

Lara
Lara

今後も新しい記法は積極的に取り入れていきたいと思いました。

コメント

タイトルとURLをコピーしました