PHPやJavaScriptの”?”を使った演算子について
はじめに
こんにちは!AITPの壱岐です。
今回はPHPやJavaScriptで使用される「?」や「??」についての紹介です。
Google検索で調べようと「PHP “?”」と調べてもなかなか検索に引っかからないあの検索のしにくい、けど使い勝手のいい機能(演算子)の紹介となります!
三項演算子条件式 ? 式1 : 式2
まずはよく見る三項演算子からです。
PHPでもJavaScriptでも同じ記述で使用できます。
PHPドキュメントから引用
(expr1) ? (expr2) : (expr3)という式は、式1 がtrueの場合に 式2 を、 式1 がfalseの場合に 式3 を値とします
条件に続いて疑問符 (?)、そして条件が真値であった場合に実行する式、コロン (:) が続き、条件が偽値であった場合に実行する式が最後に来ます。この演算子は、 if 文の代替としてよく用いられます。
MDNから引用
PHPでは5.3系列以降で使用可能で、JavaScriptではどのブラウザでもフルサポートされています。
下記のようにステップ数的には少なくなりますが、人によっては読みにくいと感じる人も居るようで賛否がある記述の仕方の一つだと思います。これについてはプロジェクトごとのコーディング規約で設定するのがいいと思います。
サンプルコード(PHPドキュメントから引用)
<?php
// 三項演算子の使用例
$action = (empty($_POST['action'])) ? 'default' : $_POST['action'];
// 上記は以下の if/else 式と同じです。
if (empty($_POST['action'])) {
$action = 'default';
} else {
$action = $_POST['action'];
}
なお、PHPの場合では、複数回重ねて使う場合、公式ドキュメントに記載がある通り、バージョンによる挙動の違いがあるため注意が必要です。(公式ドキュメント上では「”積み重ねて” 使用することは避けましょう」と注意書きがあります。)
PHP 7.4以降のバージョンで echo (true ? 'true' : false ? 't' : 'f'); のように使用した場合、下記のエラーとなります。
Fatal error: Unparenthesized
エラー内容a ? b : c ? d : eis not supported. Use either(a ? b : c) ? d : eora ? b : (c ? d : e)
とりあえずはecho ((true ? 'true' : false) ? 't' : 'f');のようにカッコをつけて優先順位を明示することでエラーの回避にはなりますが、重ねて使うとひと目で何がやりたいのかわかりにくいので、極力避けるほうがいいのかなと思います。
バージョン移行等行う場合にも注意して見ておきたいところですね!
エルビス演算子 ?:
PHPのドキュメントではエルビス演算子と明記はありませんが三項演算子の説明の箇所の箇所で説明があり、PHP5.3以降で使用可能です。
三項演算子のまんなかの部分をなくすこともできます。 式
PHPドキュメントから引用expr1 ?: expr3の結果は、expr1 がtrueと同等の場合は expr1、 それ以外の場合は expr3 となります。
下記のように使用することができます。
サンプルコード
<?php
$user = $repository->getUser(5);
$userName = $user->name ?: 'ゲスト'; // nameがTrueと同等であればその値、そうでなければ'ゲスト'が入る
echo "こんにちは!${userName}さん!";
JavaScriptの場合、?:のエルビス演算子はありませんが、||(論理和)を使用することで同じことが実現できます。 (こちらもどのブラウザでもフルサポートされています。)
サンプルコード
const user = getUser();
const userName = user.name || 'ゲスト'; // nameがtrueと同等であればその値、そうでなければ'ゲスト'が入る
console.log(`こんにちは!${userName}さん!`); // --->こんにちは!ゲストさん!
MDNから引用
expr1がtrueに変換できる場合はexpr1を返し、それ以外の場合はexpr2を返します。(中略)||演算子では論理値以外のオペランドを使用することができますが、返値が常に論理型プリミティブに変換することが可能であるため、論理演算子と見なすことができます。
とあるように、普段||は論理値で使うことが多いと思いますが、エルビス演算子のように使用することもできます。
ちなみにエルビス演算子という名前の由来はエルヴィス・プレスリーという歌手の顔文字が由来だそうです(Wikipedia)
ただし、注意点として式 expr1 ?: expr3 の結果は、expr1 が true と同等の場合は expr1、 それ以外の場合は expr3とあるようにtrue と同等かどうかの判断となるためexpr1が0のときexpr3が返却されるため思わぬ値となる可能性があります。
この場合、次に紹介するのNull合体演算子を使うほうが良いと思います。
Null合体演算子 ??
PHP でも JavaScript でも同じ記述で使用できます。
PHPでは 7.0移行のバージョンで使用可能、JavaScriptでは(IEを除き)フルサポートされています。
三項演算子と isset() を組み合わせる よくありがちなパターンを、より簡単に書くためのものです。 この演算子は、もし第一オペランドが非
PHPドキュメントから引用nullの値であればそれを返し、 そうでない場合は第二オペランドを返します。
左辺が
MDNから引用nullまたは undefined の場合に右の値を返し、それ以外の場合に左の値を返します。
PHPでは下記のように記述でき、isset()でNullかどうかを確認してNullでなければその値を使用し、
そうでなければデフォルト値をセットして使う、といった良くやりがちなことをシンプルに記述することができます。
サンプルコード
<?php
// $_GET['user'] を取得します。もし存在しない場合は
// 'nobody' を用います。
$username = $_GET['user'] ?? 'nobody';
// 上のコードは、次のコードと同じ意味です。
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
// 合体演算子を連結することもできます。次のように書くと、
// $_GET['user']、$_POST['user'] そして 'nobody'
// の順に調べて、非 &null; が定義されている最初の値を返します。
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
JavaScriptの場合、先述の||での記述をNull合体演算子に置き換えることで「0」のときの予期しない動作を回避することができます。
MDNから引用
||が論理演算子であるため、左辺の値は評価によって強制的に論理値になり、偽値(0,'',NaN,null,undefined)が返されることはありません。
この動作は、0や'',NaNを有効な値と考えている場合、予期せぬ結果を引き起こす可能性があります。 Null 合体演算子は、左辺の値がnullもしくはundefinedのどちらか(その他の falsy な値は含みません)に評価された場合にのみ右辺の値を返すことで、この潜在的な危険を回避します。
(PHP)?-> Nullsafe演算子
PHP用の演算子で、PHP8.0からで使用可能です。
オブジェクトが
PHPドキュメントから引用nullと評価された場合に、例外はスローされず、nullが返される(中略)
オブジェクトの評価がチェインの一部だった場合は、 残りのチェインはスキップされます。
is_null()でnullかどうかチェックをして…と繰り返し記述しなければならなかったものが、nullsafe演算子を使用することでかなりスッキリとした記述にできることがわかると思います。
サンプルコード
<?php
// PHP 8.0.0 以降は、このように書けます:
$result = $repository?->getUser(5)?->name;
// これは、以下のコードチェックと同等です:
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);
if (is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}
また前述のNULL合体演算子と併用して下記のように使用することで、$nameはNullになることはないので、後続の処理でNullかどうか気にすることなく処理することができます。
<?php
$name = $repository?->getUser(5)?->name ?? '';
(JavaScript).?オプショナルチェーン
JavaScript の演算子で、(IEを除き)フルサポートされています。
接続されたオブジェクトチェーンの深くに位置するプロパティの値を、チェーン内の各参照が正しいかどうかを明示的に確認せずに読み込むことを可能にします。(中略)
MDNから引用
参照が nullish またはundefinedの場合にエラーとなるのではなく、式が短絡されundefinedが返される (中略)
関数呼び出しで使用すると、与えられた関数が存在しない場合、undefinedを返します。
サンプルコード
const userName = getUser(123)?.name;
// オプショナルチェーンを使わない場合(色々やり方はあると思います)
let userNameOldVer = '';
const user = getUser(123);
if (typeof(user) != 'undefined') {
if ('name' in user) {
userNameOldVer = user.name;
}
}
おわりに
Null合体演算子とNullsafe演算子・オプショナルチェーンの組み合わせを使うことで、ほぼNull周りのことは考えずに済むので、個人的にはどんどん使っていきたいと思っています。
うまく使って安全でスマートな記述をしていけるようにしていきましょう!