ある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) {
// ...
}
}
}
...最初より長いじゃん!!