PHP

is_numeric を使う

前にis_numeric()の挙動を調べようとググったら「使うべきではない」なんて記事を見つけてしまったのでメモしました。

is_numeric( $var ) は、第一引数が数値か数値の文字列かを調べて真偽値を返す関数です。
PHP is_numeric - Manual

$var = '20210601';
var_dump ( is_numeric($var) );
> bool(true)

で、変数の型が文字列である場合でも真を返すのはいかがなものか、16進数の文字列まで真を返してはエラーの原因になりはしないか、というのが意見としてあるようです。

話によればPHPの開発にあたってはPerlが参考になっていると聞いたことがあります。Perlではモジュールを除いた組み込み関数でis_numeric()に該当するのはないと思いますが、数値や数値の文字列はPHPと同じように一緒くたに扱えます。これは便利な時もあれば不便なときもありますが、変数の型に厳格な言語とは違うというこの前提を承知しておけば基本的にトラブルは避けられるのではないかと思います。

よく使われている WordPress を例に。WordPress で投稿のカスタムフィールドの値を取得する場合、文字列か配列のどちらかで得ることになります。たとえ数値しか扱わないつもりのデータでも、受け取るのは文字列です。

// ループの中で投稿のカスタムフィールド 'my_custom_num' の値を取得
$num = get_post_meta( $post->ID, 'my_custom_num', true );

// カスタムフィールドがあれば表示 ※ '0'が指定されてあれば表示できない
if ( $num ) {
  echo "my_custom_num の数値は{$num}です。";
}

カスタムフィールドの値を取得する get_post_meta() では、フィールドの値が未設定の場合は空文字が返ってきます。ただし、値に'0'(ゼロ)が指定されていればちゃんと数値の文字列が返ってきます。文字列の'0'は数値のゼロと同じなので、上記のようにフィールドの値が設定されているか否かを真偽で判定すると空文字と同じ偽と判定される問題が起きます。

Perl を使っていればこのあたりの処理は慣れっこですが、実は不便な場合のほとんどはこのゼロの扱いではないかと思います。PHPではこういう場合に is_numeric() が便利です。数値であっても文字列であっても'0'と空文字を判定できます。

// ループの中で投稿のカスタムフィールド 'my_custom_num' の値を取得
$num = get_post_meta( $post->ID, 'my_custom_num', true );

// カスタムフィールドがあれば(たとえ0であっても)表示
if ( is_numeric($num) ) {
  echo "my_custom_num の数値は{$num}です。";
}

ということで、'0'を含む場合の真偽値の判定やその後の計算でエラーを起こさないための書式判定には便利に動作します。

気を付けないといけないのは、0-9からなる10進数の数値のみを期待するところに、is_numeric() をすり抜けた16進数が混ざることでしょうか。この場合、正規表現で数値以外を検索するとか整数に変換した値と比較するとかも考えられますが、0-9の数値のみの16進数が来てしまってはどうしようもないので、そもそも10進数を期待するところに16進数が入らないようにするのがベストだと思います。

// 16進数
$hex = array('1.3e3', '101010' );

foreach ( $hex as $num ) {
  // 数値または数値の文字列
  if ( is_numeric($num) ) {
    echo "{$num}は数値です。<br />";

    // 0-9以外を含む数値
    if ( ! preg_match('/^[\d\.]+$/', $num) ) {
      echo "ただし、{$num}は10進数ではないようです。<br />";
    }
    // 0-9のみで構成された数値
    else {
      echo "たしかに{$num}は0-9.のみで構成された数値ではあるけど、だからって10進数か16進数かなんて僕にはわかりません。<br />";
    }
  }
}

ということで、is_numeric()の挙動には問題ないんじゃないでしょうかというお話でした。