最近 WordPress を触る機会が復活してきて PHP の書き方忘れまくってるので TIL 的にメモを残しておこうと思いました。
オプショナルな仮引数を持つ関数内で変数の初期値を設定したい
<?php function myFunc($x = null) { // $foo に初期値を設定したい $foo = $x; var_export($foo); }
環境
- PHP
v8.1.12
- PHP Sandbox https://onlinephp.io/ で実行
🙅 ?:
三項演算子の省略形は falsy な値も弾かれてしまう
$x = $a ?: $b
は $x = $a ? $a : $b
と同等。
この時 $a ?
は !empty($a) ?
と同等なので falsy な値が引数として渡された時にデフォルト値が使われてしまう
<?php function myFunc($x = null) { $foo = $x ?: 'default'; var_export($foo); } myFunc(); // => 'default' myFunc(1); // => 1 myFunc(null); // => 'default' myFunc(false); // => 'default' myFunc(0); // => 'default' myFunc(''); // => 'default' myFunc([]); // => 'default'
Falsy な値の時もデフォルト値を使いたい場合は良いと思うけど、その挙動にしていることを覚えてないとバグの温床になりそうな気がする。
🙆 ??
null合体演算子を使うのが良い (PHP v7.x 以上)
$x = $a ?? $b
は if (isset($a)) { $x = $a } else { $x = $b }
と同等。
<?php function myFunc($x = null) { $foo = $x ?? 'default'; var_export($foo); } myFunc(); // => 'default' myFunc(1); // => 1 myFunc(null); // => 'default' myFunc(false); // => false myFunc(0); // => 0 myFunc(''); // => '' myFunc([]); // => array()
isset()
なので引数に null
を意図的に渡した場合はデフォルト値が返される
cf. PHP 7 変数に値がなければデフォルト値を設定したい。 - かもメモ
PHP v7.4 以上では ??=
null合体代入演算子 が使える
$a ??= $b
… $a
が null
なら $a
に $b
を代入する。
<?php $a ??= $b; // 下記と同等 $a = $a ?? $b;
null合体代入演算子 ??=
を使うと下記の用に書き換えられる
<?function myFunc($x = null) { $x ??= 'default'; var_export($x); } myFunc(); // => 'default' myFunc(1); // => 1 myFunc(null); // => 'default' myFunc(false); // => false myFunc(0); // => 0 myFunc(''); // => '' myFunc([]); // => array()
個人的に null合体代入演算子 ??=
は元の引数の状態を変えてしまうのであまり好みではないかも。
引数を型で縛る
そもそも引数に何でも渡せてしまうのがバグ発生の元。
PHP の型宣言を使って入力値を絞ってしまえば関数内での考慮することがシンプルになります
PHP5から「タイプヒンティング(型宣言)」は存在したが、そこからずっと中途半端な状態が続きようやくPHP7で機能拡張されたことによってスカラ型の宣言も行えるようになった。 また、PHP7.2からobject型も扱えるようになっている。
cf. [PHP] 型宣言(PHP7) - Qiita
null を許容する型宣言
nullable (PHP v7.1 以降)
型名の前に ?
を付けると nullable な型になる
<?php function myFunc(?int $x): void { $foo = $x ?? 'default'; var_export($foo); } myFunc(10); // => 10 myFunc(null); // => 'default' myFunc(""); // => Fatal error: Uncaught TypeError: myFunc(): Argument #1 ($x) must be of type ?int myFunc(); // => Fatal error: Uncaught ArgumentCountError: Too few arguments to function myFunc(), 0 passed
nullable な型は null
を渡すことができるがデフォルト引数を設定してないとオプショナルな引数という感じではない
そもそも null
を明示的に渡したい理由がある場合に使ったほうが良さそう
デフォルト引数だけで null を許容する
仮引数の型宣言があってもデフォルト引数で null
を設定することができる
これを利用すると型付のオプショナルな引数を作れる
<?php function myFunc(int $x = null): void { $foo = $x ?? 'default'; var_export($foo); } myFunc(); // => 'default' myFunc(10); // => 10 myFunc([]); // => Fatal error: Uncaught TypeError: myFunc(): Argument #1 ($x) must be of type ?int,
⚠ PHP の型宣言は型を厳密にチェックしてるのではない!?
先の例を試していて int: $x
と型宣言をしているにも関わらず 1
や null
, true
, false
などは型エラーにならず関数が実行されてしまいました。
<?php function myFunc(int $x = null): void { $foo = $x ?? 'default'; var_export($foo); } myFunc(); // => 'default' myFunc(10); // => 10 // 型が異なるが実行されてしまう myFunc('1'); // => 1 myFunc(null); // => 'default' myFunc(true); // => 1 myFunc(false); // => 0 // float は warning が出るが int に変換されて実行される myFunc(1.2); // => 1 // Deprecated: Implicit conversion from float 1.2 to int loses precision myFunc('-2.5'); // => -2 // Deprecated: Implicit conversion from float-string "-2.5" to int loses precision // 型エラーになるもの myFunc(''); // Fatal error: Uncaught TypeError: myFunc(): Argument #1 ($x) must be of type ?int, string given myFunc([]); // Fatal error: Uncaught TypeError: myFunc(): Argument #1 ($x) must be of type ?int, array given class Bar { public $x = 1; }; $bar = new Bar(); myFunc($bar); // Fatal error: Uncaught TypeError: myFunc(): Argument #1 ($x) must be of type ?int, Bar given
挙動を見ている感じ暗黙の型変換が行えるかどうかをチェックしているように感じました。
TypeScript などのような型でのチェックができる訳ではなさそうなので注意が必要そうです
結論: オプショナルな引数の初期値を設定した関数の作成
PHP 7 以上であれば関数の仮引数に 型宣言 + デフォルト引数に null
を設定して、null 合体演算子を使ってデフォルト値を与えるのが良さそう
<?php function myFunc(int $x = null): void { $a = $x ?? 'default value'; // ... }
⚠ ただし PHP の型宣言は型変換が可能なものは通ってしまうので注意が必要
おわり ₍ ᐢ. ̫ .ᐢ ₎
追記 厳密な型チェックの方法を教えてもらいました!
[参考]
- PHP Sandbox - Execute PHP code online through your browser
- PHP: 新機能 - Manual
- PHP: Null合体代入演算子(??=)の使い所を考えてみた - Qiita
- [PHP] 型宣言(PHP7) - Qiita
- 【PHP】引数の二種類の NULL 許容型の宣言方法とその違い - Qiita
- 【PHP8.0】PHPに名前付き引数が実装される - Qiita
- PHP 7.4 の新機能を試してみよう | Ritolabo
- 【PHP】知らなかった比較演算子【null合体演算子】 | とものブログ
- PHP の型宣言は変数の型のチェックではない - Qiita