PHPには、連想配列から値の入った変数を一気に作る関数 extract() があって便利なんですが、注意するところがいくつもあります。
便利だからといって気軽に使って良いものではありません。コーディング規約によっては使うだけでエラーになるものもあります。
そんな extract() 使用の注意点を見ていきます。
extract() の動きを見てみよう。
extract() はサンプルコードを見ると一発で分かります。
<?php
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr);
echo $val1 . PHP_EOL;
echo $val2 . PHP_EOL;
echo $val3 . PHP_EOL;
sample1
sample2
sample3
配列の値を見るよりシンプルな変数名で見れるので楽です。そして1行で変数を作ってしまうところが圧巻。
でも、かんたんで便利なんですけど、一気にやってもらうところに注意が必要です。
グローバル変数で使うと危ない
一番注意が必要なのは、グローバル変数に展開する場合です。
グローバル変数とは、関数やクラスの外で宣言される変数。PHPプログラムが起動して終了するまでどこからでも参照できる変数です。
連想配列の要素名が決まっているのならまだいいですが、可変の場合、グローバル変数が変わっていくプログラムになってバグが起きやすい。
何より、その要素名(作られる変数名)によっては、何か悪いことをしようという値をいつでも参照できる変数に展開してしまいます。
そして最悪なのは、既存のグローバル変数と同じ変数名を連想配列に悪意をもって挿入することも可能で、そのグローバル変数の破壊すらできます。
プログラム全体に悪影響を及ぼす変数を作ってるかもしれないというのをコーディングする人は意識しなければいけません。
外部から入力されるデータの展開は危ない
グローバル変数に並んで注意が必要なのは、外部から入力される配列データを展開する場合です。
たとえば、URLクエリーの値を連想配列で使いますが、それをそのまま extract() に入れてしまうのはマズい。
URLクエリーはかんたんに改ざんして送ることが可能なので、悪い人は頻繁に不正アクセスを試みます。Webサーバーのログを見るとその形跡はよく出てくる。
それくらい危険なデータをそのまま変数に展開してしまう extract() には注意が必要。
extract() を使う直前に必ずデータチェックを行わないといけません。
ね? 気軽に使うもんじゃないでしょ?
PHPドキュメントでもよく見えるように警告を発しています。
ユーザーの入力、例えば $_GET や $_FILES のような、 信頼できないデータに extract() を使用しないでください。
PHP公式ドキュメント
PHP公式ドキュメント
既存の変数に上書きするかもしれない
extract() の初期設定は既存変数の上書きです。さっきのサンプルを少し修正して確認しましょう。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr);
echo $val1 . PHP_EOL;
echo $val2 . PHP_EOL;
echo $val3 . PHP_EOL;
sample1
sample2
sample3
extract() のパラメータには変数名の衝突が起きたときどうするか? の設定があるので、上書き禁止にしてみましょう。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_SKIP);
echo $val1 . PHP_EOL;
echo $val2 . PHP_EOL;
echo $val3 . PHP_EOL;
test
sample2
sample3
このように、連想配列からシンプルに変数展開としてextract() を使うとき、パラメータに配列だけを指定しているものは要注意です。
『本当に上書きでいいの?』と疑ってかかりましょう。
そもそも extract() は、配列データのマージ(結合)に本来の力を発揮する関数です。上書きは当然の動き。
すべてのパラメータに明確な指定が見えないものには、バグ潜在の可能性があることに気をつけましょう。
(もちろん、はっきりと意思表示して配列のみ指定する場合もある。)
展開した変数名は漏れなく把握すること。
extract() を気軽に使うと、展開した変数名が分からなくなることがあります。これがいけない。
プログラムを書いた本人が把握してない変数を他の人が分かるわけないし、そのプログラムは色んな理由で怖い。
そんなことからコーディン規約ではエラーとして扱うこともあるくらいです。
どんな変数が作られるのか把握して、上書きするのかしないのかはっきりさせましょう。
それと、extract() に指定する配列データのチェックが必要です。悪いことをする人には狙われやすいので、不穏な要素名が入ってる配列データは消すような作業も必要でしょう。
個人的には連想配列の要素名が明確になってるものだけに使うべきだと思います。たとえばこんな感じの。
function test( args ) {
extract( array(
'val1' => args[0],
'val2' => args[1],
'val3' => args[2],
));
}
また extract() は、関数などの処理の最初に書くほうがいいでしょうね? 変数名の衝突を確実に避けるために。
プレフィックス(prefix)を付ける機能もある
extract() が展開する変数名にはプレフィックス(接頭辞)を付けることができます。そうすれば既存の変数やその後に宣言する変数との共存や、衝突を避ける手助けになる。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_PREFIX_ALL, 'extr');
echo $val1 . PHP_EOL;
echo $extr_val1 . PHP_EOL;
echo $extr_val2 . PHP_EOL;
echo $extr_val3 . PHP_EOL;
test
sample1
sample2
sample3
これだと extract() を使って展開したんだな、と分かりますからね?
プレフィックスの後ろにはアンダースコア(_)が付くことに注意してください。
だからといって、便利だから気軽に extract() を使えますよ? とはいえません。
プレフィックスが付こうが付くまいが、展開される変数は把握しないといけないし、動的(連想配列の要素名が一定じゃない)なものを展開するには要注意です。
PHP公式ドキュメント