Posted at

PHP array キャストのススメ

More than 3 years have passed since last update.

ある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) {
// ...
}
}
}

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