31
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP array キャストのススメ

Posted at

ある1つの関数で、単一の値でも、複数の値でも受け取りたいというニーズは割とあると思います。

対象が1個なら、可変長引数 ...func_get_args() の方が楽ですが、2個以上になると、それぞれちゃんとお世話しなければいけません。
例えば私は、以下のように書いてました。

function hoge($fuga, $piyo) {
  // 配列じゃなかったら配列にする
  if (!is_array($fuga)) $fuga = [$fuga];
  if (!is_array($piyo)) $piyo = [$piyo];

  foreach($fuga as $f) {
    foreach($piyo as $p) {
      print "$f <-> $p\n";
    }
  }
}

hoge(1, 1);             // "1 <-> 1"
hoge(1, [1, 2, 3]);     // "1 <-> 1", "1 <-> 2", "1 <-> 3"
hoge([1, 2, 3], 1);     // "1 <-> 1", "2 <-> 1", "3 <-> 1"
hoge([1, 2], [1, 2]);   // "1 <-> 1", "1 <-> 2", "2 <-> 1", "2 <-> 2"

is_array は、やってることは分かり易いんですが、冗長な気もしなくもない…。

array へのキャスト

もっと楽ちんに実装できないものかと、PHPのマニュアルをめくると、以下の記述を発見。

integer, float, string, boolean, resourceのいずれの型においても、 array に変換する場合、 最初のスカラー値が割り当てられている一つの要素 (添字は 0) を持つ配列を得ることになります。

... PHP: 配列

つまりどういうことかと言うと、

(array)1;         // == [1]
(array)0.5;       // == [0.5]
(array)'string';  // == ['string']
(array)true;      // == [true]
(array)[1,2,3]    // == [1, 2, 3]

// resource 型
$fp = fopen("/dev/null", "r");
(array)$fp;       // == [$fp]

スカラー値を取る型に対して array キャストすると、それを配列にラップしてくれます。勿論 array の場合は array のまま。

つまり、やってる事は if (!is_array($fuga)) $fuga = [$fuga]; とほぼ一緒です。
よって、冒頭の関数は、以下のように書き換えられます。

function hoge($fuga, $piyo) {
  foreach((array)$fuga as $f) {
    foreach((array)$piyo as $p) {
      print "$f <-> $p\n";
    }
  }
}

コードもスッキリしました。やった!

要注意の値/型

null 値

$fuga = null;

if (!is_array($fuga)) [$fuga];    // [null]
(array)$fuga;                     // []

null 値を array にキャストすると、[null] ではなく、空要素の配列 [] が返ります。

"関数の引数として扱う" という場面だと、"foreach でループが回らなくなる = 処理しない"
という意味になるので、むしろこっちの方が好都合かもしれませんが、is_array の時とは挙動が違うので要注意です。

※null をラップする必要がある場合は、[null] と明示する必要があります

object 型

より注意が必要なのは object 型です。例えば、クラスのインスタンスを関数に渡す場合には、array キャストした時の挙動が他の型と異なります。

objectを配列にする場合には、配列の要素として オブジェクトの属性 (メンバ変数) を持つ配列を得ることになります。

... PHP: 配列

// object(stdClass)
$fuga = json_decode('{"hoge": 1, "fuga": 2}');

if (!is_array($fuga)) [$fuga];    // [object(stdClass)]
(array)$fuga;                     // ["hoge" => 1, "fuga" => 2]

object を配列でラップするのではなく、インスタンスが持つメンバ変数を、連想配列に変換する動きになってしまいます。これでは、先ほど書き換えた関数も、想定通りの動きにはなりません。

使いどころ

結論、引数の型によって使い分けが必要な感じです。

  • スカラー値 を渡す関数なら、(array) キャストで楽する
  • object を渡す関数なら、素直に is_array で判定する

処理速度

テキトーに100万回ベンチを取ってみましたが、単一引数でラップする必要がある場合、 (array) キャストが 3~4割 ほど早くなる模様です。

一方で、ラップの必要が無い (はじめから配列) の場合は、配列を再生成する分のオーバーヘッドが無くなるため、差はほぼ皆無でした。

とはいえ、このへんは元々の処理が一瞬なので、普通に扱う分には全然気にしなくて良いでしょう。

オマケ: どっちにも対応させる

あんまりケースとしては少ないと思いますが、"スカラー値も object も取り得て、同じようにラップさせたい!!" という場合には、以下のようにすると良いかも。

function hoge($fuga, $piyo) {
  $fuga = is_object($fuga) ? [$fuga] : (array)$fuga;
  $piyo = is_object($piyo) ? [$piyo] : (array)$piyo;

  foreach($fuga as $f) {
    foreach($piyo as $p) {
      // ...
    }
  }
}

...最初より長いじゃん!!

31
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?