PHPには連想配列から値の入った変数を一気に作る関数extract()があって便利なんですが、デフォルトでは既存の変数名とかぶると上書きしてしまいます。
それを回避するオプションがあるので、色々パターンを試してどういう動きをするのか見てみましょう。
本当に上書きでいいの? デフォルト設定の危うさ
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() はコピペして動くからOKと安易に使う関数じゃありません。きちんとパラメータの意味を知らなきゃいけないものです。
ということで、上書き禁止も含めていろいろな変数展開のオプション設定を見ていきます。
シンプルな上書き禁止
まず最初は、シンプルな上書き禁止のオプションです。パラメータ flags の指定を変えます。
<?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
すでにいくつもの変数を使って処理をしていて、足りないデータを後で付け足す処理になります。
ちなみに初期設定では、この flags が EXTR_OVERWRITE になっているから上書きされます。
既存変数だけを上書きする
上書きにはもうひとつ、すでに宣言済みの変数だけを上書きするものがあります。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_IF_EXISTS);
if (isset($val1)) {
echo $val1 . PHP_EOL;
}
if (isset($val2)) {
echo $val2 . PHP_EOL;
}
if (isset($val3)) {
echo $val3 . PHP_EOL;
}
sample1
EXTR_IF_EXISTS を指定したとき、既存変数がないものは展開されません。$val2, $val3 は isset() 抜きで使用すると、『NOTICE: Undefined variable』の警告が発生します。
このオプションは、途中で変数の値を一気に更新する際に使います。余計な変数を作らないのもいいですね?
かぶったものだけ変数名を変える。
extract() を使った変数名にプレフィックス(接頭辞)を付けるパラメータもあります(prefix)。
これを使って、変数名がかぶったものだけを別変数に退避させましょう。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_PREFIX_SAME, 'bak');
echo 'val1 => ' . $val1 . PHP_EOL;
echo 'val2 => ' . $val2 . PHP_EOL;
echo 'val3 => ' . $val3 . PHP_EOL;
if (isset($bak_val1)) {
echo 'val1 bak => ' . $bak_val1 . PHP_EOL;
}
if (isset($bak_val2)) {
echo 'val2 bak => ' . $bak_val2 . PHP_EOL;
}
if (isset($bak_val3)) {
echo 'val1 bak => ' . $bak_val3 . PHP_EOL;
}
val1 => test
val2 => sample2
val3 => sample3
val1 bak => sample1
展開する変数名のすべてにプレフィックスを付ける
extract() で展開する変数名のすべてにプレフィックスを付けるオプションもあります。
<?php
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_PREFIX_ALL, 'next');
echo 'next val1 => ' . $next_val1 . PHP_EOL;
echo 'next val2 => ' . $next_val2 . PHP_EOL;
echo 'next val3 => ' . $next_val3 . PHP_EOL;
next val1 => sample1
next val2 => sample2
next val3 => sample3
よく同じデータ群の変数で、変数名の一部を共通にする場合がありますが、これで一気に作れます。
EXTR_SKIP や EXTR_IF_EXISTS は和算(|)で併用できる気もしましたができませんでした。
EXTR_PREFIX_ALL | EXTR_IF_EXISTS = EXTR_PREFIX_IF_EXISTS
和算はできませんが、既存変数がある場合のみプレフィックスを付けたいときは EXTR_PREFIX_IF_EXISTS でできます。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_PREFIX_IF_EXISTS, 'next');
if(isset($val1)) {
echo 'val1 => ' . $val1 . PHP_EOL;
}
if(isset($next_val1)) {
echo 'next val1 => ' . $next_val1 . PHP_EOL;
}
if(isset($next_val2)) {
echo 'next val2 => ' . $next_val2 . PHP_EOL;
}
if(isset($next_val3)) {
echo 'next val3 => ' . $next_val3 . PHP_EOL;
}
val1 => test
next val1 => sample1
既存変数名はプレフィックスが付いてないことに注意してください。
試しに既存変数名をプレフィックス付加後の変数名($next_val1)にすると不思議な結果が出ます。
extract() の本領。元の連想配列と連動した変数を展開する
パラメータに EXTR_REFS を使うと、extract() で展開した変の数値を変えると、その変更が元の連想配列まで反映されます。
<?php
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_REFS);
var_dump($arr);
if(isset($val1)) {
$val1 = 'sample1 change';
}
if(isset($val2)) {
$val2 = 'sample2 change';
}
var_dump($arr);
var_dump(get_defined_vars());
array(3) {
["val1"]=>
&string(7) "sample1"
["val2"]=>
&string(7) "sample2"
["val3"]=>
&string(7) "sample3"
}
array(3) {
["val1"]=>
&string(14) "sample1 change"
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
array(11) {
・
省略
・
["arr"]=>
array(3) {
["val1"]=>
&string(14) "sample1 change"
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
["val1"]=>
&string(14) "sample1 change"
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
連想配列を編集するときの変数を[]付きで書くのが面倒なとき便利。これまでとひと味違い、extract() やるじゃん!という機能です。
『どうやったらそうなるの?』と思うかもしれませんが、PHP変数に用意されている変数のリファレンスを利用しています。
実行結果に変数のリストも出力しました。それを見るとデータ型にアンパサンド(&)が付いてます。これがリファレンス変数を意味します。
EXTR_REFS だけは他のフラグと和算で併用できます。
<?php
$val1 = 'test';
$arr = [
'val1' => 'sample1',
'val2' => 'sample2',
'val3' => 'sample3',
];
extract($arr, EXTR_REFS | EXTR_SKIP);
var_dump($arr);
if(isset($val1)) {
$val1 = 'sample1 change';
}
if(isset($val2)) {
$val2 = 'sample2 change';
}
var_dump($arr);
var_dump(get_defined_vars());
array(3) {
["val1"]=>
string(7) "sample1"
["val2"]=>
&string(7) "sample2"
["val3"]=>
&string(7) "sample3"
}
array(3) {
["val1"]=>
string(7) "sample1"
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
array(11) {
・
省略
・
["val1"]=>
string(14) "sample1 change"
["arr"]=>
array(3) {
["val1"]=>
string(7) "sample1"
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
["val2"]=>
&string(14) "sample2 change"
["val3"]=>
&string(7) "sample3"
}
展開されなかった変数($val1)はリファレンスではなく通常の変数です。
(連想配列のval1要素も。)
このオプションが extract() で一番使える機能じゃないかな?
PHP公式ドキュメント