PHPのnullsafe演算子は「短絡」する — ?-> の本当の挙動と使いどころ

nullsafe演算子 ?-> は、左辺がnullならそこから先の呼び出しを一切評価せずに式全体をnullにするPHP 8.0の演算子です。単なる「nullチェックの短縮形」ではありません。

この「そこから先を評価しない」という部分、意外と誤解されている気がします。私も最初は $a?->b()?->c() を「毎回nullチェックが挟まる書き方」くらいに思っていて、引数の副作用でハマったことがありました。

短絡評価とは何が起きているのか

まず挙動を確認します。nullsafeがnullを踏んだ瞬間、その後ろのメソッド呼び出しも、その引数の評価も止まります。

<?php
declare(strict_types=1);

function log_it(string $msg): string
{
    echo "評価された: {$msg}\n";
    return $msg;
}

$user = null;

// 何も出力されない。$result は null。
$result = $user?->profile->rename(log_it('hello'));

var_dump($result); // NULL

普通の演算子なら引数は先に評価されます。nullsafeは式の途中で打ち切るので、log_it() が呼ばれません。ログ出力やカウンタのインクリメントを引数に忍ばせていると、静かに実行されなくなります。

逆に言えば、これがあるおかげで $a?->b->c->d のように途中を ?-> にしなくても最初の一箇所だけで済みます。全部のアローを ?-> にするのは、本当にその都度nullになりうるときだけで十分です。

なぜ代入の左辺には書けないのか

これはコンパイルエラーになります。

<?php
$user?->name = 'のり';
// Fatal error: Can't use nullsafe operator in write context

「nullなら代入をスキップする」という意味に見えますが、実装上の都合に加えて、右辺をいつ評価するのかという意味論がはっきりしないため、書き込み文脈では丸ごと禁止されました。unset() や参照渡しの引数に渡すのも同様に不可です。個人的には、ここを禁止にしたのは正解だったと思います。代入が黙って消えるコードは追いかけようがありません。

配列の添字にnullsafeは使えない

よくある勘違いがこれです。

<?php
$config = ['db' => ['host' => 'localhost']];

// これは書けない(そういう構文はない)
// $config?['db'];

// 配列は従来どおり null合体演算子で
$host = $config['db']['host'] ?? '127.0.0.1';

nullsafeはオブジェクトのプロパティ・メソッドアクセス専用です。配列や未定義キーは ?? の担当。逆に、オブジェクトの後ろに続く添字アクセスは短絡の対象になります。$user?->tags['first'] は、$user がnullなら添字アクセスごと飛ばされてnullになります。

実務ではどこまで使っていいのか

便利ですが、私は「本当にnullがドメイン上ありうる場所」だけに限定しています。理由は単純で、?-> を機械的に並べると、null由来のバグが例外ではなくnullとして下流に流れていくからです。

<?php
// 悪くない: 「退会済みなら組織なし」がドメインの仕様
$orgName = $user->organization?->name ?? '(所属なし)';

// 危うい: 本来必ず存在するはずのものを ?-> で隠している
$total = $order?->payment?->amount?->value;

下の書き方だと、$order が取れていない不具合も、支払いが未登録という正常系も、同じnullになって区別できません。落ちるべきところは落としたほうが、結局デバッグは早く終わります。

静的解析との相性

PHPStanやPsalmを入れていると、nullになりえない値に ?-> を書いた時点で「この演算子は不要」と指摘されます。これはかなり有用な警告で、?-> が付いている箇所は「ここは本当にnullが来る」という型レベルの宣言として読めるようになります。逆に指摘を握りつぶしていると、その情報が失われます。

なお、戻り値の型が ?Foo のメソッドを ?-> なしで繋ぐと解析エラーになるので、型宣言をきちんと書いておくほど ?-> の要否は自動で判定してもらえます。

まとめ

nullsafe演算子は、nullチェックの糖衣というより「そこから先の評価を打ち切る」演算子です。引数の副作用が飛ぶこと、書き込み文脈では使えないこと、配列には効かないこと。この三つを押さえておけば、だいたいの事故は避けられると思います。あとは使いすぎないこと。nullを握りつぶす道具ではなく、nullが仕様として存在する場所を示す印として使うのが、いちばん収まりがいい気がしています。

よくある質問

Q. nullsafe演算子はどのバージョンから使えますか?
A. PHP 8.0からです。それ以前の環境では構文エラーになるため、ライブラリなどで古いPHPをサポートする場合は使えません。

Q. ?->?? はどう使い分けますか?
A. ?-> はオブジェクトのプロパティ・メソッドアクセス、?? は未定義変数・未定義配列キー・null値のフォールバックです。$user?->name ?? '匿名' のように併用するのが実務では多いです。

Q. メソッドの戻り値がnullだった場合も安全ですか?
A. その戻り値にさらにアクセスするなら、そこにも ?-> が必要です。$a?->b()->c() は、b() がnullを返した時点でエラーになります。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です