ぐらめぬ・ぜぷつぇんのはてダ(2007 to 2011)

2007年~2011年ごろまで はてなダイアリー に書いてた記事を引っ越してきました。

extract()を組み合わせてみる。結構・・・イケル、かも。

http://jp.php.net/manual/ja/function.extract.php
extract()はあまり使われないし、ユーザー入力と組み合わせた日にはセキュリティ的にヤバイ事になりそうですしで、自分も会社の絡みでとある記事を書くまでは全く知りませんでした。

PHP/連載/007 : PHPと戯れる(7)「PHPでの配列と変数操作関数 (3)」の"おまけ"の項
http://www.glamenv-septzen.net/pukiwiki/index.php?PHP%2F%CF%A2%BA%DC%2F007

で、今回ふと、使ってみようかと思った次第。

実験1:グローバルスコープでのeval()との組み合わせ結果

まずは簡単な実証実験。
extract_1.php :

<?php
class Hoge {
    var $_v = 0;
    function Hoge($v) { $this->_v = $v; }
}
$hoge =& new Hoge(123);

$str1 = "abc";
$str2 = "HOGEHOGE";
$var1 = 123;
$var2 = 456;

$GLOBALS["Test Vars"] = array(
    "var1" => $var1,
    "var2" => &$var2,
    "str1" => $str1, 
    "str2" => &$str2,
    "hoge" => &$hoge,
);

ob_start();
?>
// このコードでは、下のextractはあろうと無かろうと一緒。
//extract($GLOBALS['Test Vars']);
echo "=========================== 1\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
$str1 = "ABC";
$str2 = "foobar";
$var1 = 789;
$var2 = 654;
$hoge->_v = 456;
echo "=========================== 2\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
<?php
$code = ob_get_contents();
ob_end_clean();

eval($code);
echo "=========================== 3\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
?>

実行してみます。

> php extract_1.php
=========================== 1
string(3) "abc"
string(8) "HOGEHOGE"
int(123)
int(456)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(123)
}
=========================== 2
string(3) "ABC"
string(6) "foobar"
int(789)
int(654)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}
=========================== 3
string(3) "ABC"
string(6) "foobar"
int(789)
int(654)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}

まず、掲示したソースの"extract"の部分に注目。これ、今回のケースではあっても無くても結果は変わりません。
というのは、このevalブロック自体がグローバルスコープ(関数の中でもなく、クラスのメソッドの中でもない領域)で実行されている為、evalブロックの「外」にある変数は自動的にグローバルスコープに入り、特にextractでごにょごにょせずとも、eval()ブロックのなかでグローバルスコープの変数として普通に参照できるわけです。

実際、出力を見てみると eval() の実行後、外に出た後もeval()の中の変更が反映されています。見ているところが一緒というわけです。

では、関数の中ではどうでしょう?

実験2:関数の中でeval()してみる。

extract_2.php :

<?php
class Hoge {
    var $_v = 0;
    function Hoge($v) { $this->_v = $v; }
}
$hoge =& new Hoge(123);

$str1 = "abc";
$str2 = "HOGEHOGE";
$var1 = 123;
$var2 = 456;

$GLOBALS["Test Vars"] = array(
    "var1" => $var1,
    "var2" => &$var2,
    "str1" => $str1, 
    "str2" => &$str2,
    "hoge" => &$hoge,
);

function ev() {
    $localv = "XYZ";

    ob_start();
    ?>
    // 関数の中に入ると、グローバルスコープから外れるので、下のextractが無いと変数を引き継げない。
    extract($GLOBALS['Test Vars']);
    echo "=========================== 1\n";
    echo $localv . PHP_EOL;
    var_dump($str1, $str2, $var1, $var2, $hoge);
    $str1 = "ABC";
    $str2 = "foobar";
    $var1 = 789;
    $var2 = 654;
    $hoge->_v = 456;
    echo "=========================== 2\n";
    var_dump($str1, $str2, $var1, $var2, $hoge);

    <?php
    $code = ob_get_contents();
    ob_end_clean();

    eval($code);
}

// 実行
ev();
echo "=========================== 3\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
?>

実行してみます。

> php extract_2.php
=========================== 1 ... evalブロックのextract()の直後
XYZ                           ... $localvの値が採れている。
string(3) "abc"
string(8) "HOGEHOGE"
int(123)
int(456)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(123)
}
=========================== 2 ... evalブロック内で値を書き換えた直後
string(3) "ABC"
string(6) "foobar"
int(789)
int(654)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}
=========================== 3 ... evalブロックの実行が終わった後(「外」)
string(3) "abc"
string(8) "HOGEHOGE"
int(123)
int(456)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}

今回はextractが無いと、"1"の時点で中身が空っぽ、つまり未定義です。
また、"3"で抜けた後、値が変わっていないことから、例えextract()に参照を設定した配列を渡そうと、どうやらeval()の中の変数はコピーを参照してしまっているようです。

どうでもイイですが、PHPでは"参照"という機構が結構分かりづらくて特殊なので、こうして普通の文章で変数の参照云々を書こうとすると自分自身曖昧になってやりづらいものがありますね・・・。

まあ良いでしょう。ある意味、Perlのlocal相当と見なすこともできます。恐らくいろんなバリエーションで使い始めると、おかしい事だらけになってくるでしょうが・・・今回は、要は「とば口」を開く事がどちらかというと主目的なので、これで充分です。

では最後はcreate_functionだとどうなるか、です。

実験3:create_functionで応用してみる。

extract_3.php :

<?php
class Hoge {
    var $_v = 0;
    function Hoge($v) { $this->_v = $v; }
}
$hoge =& new Hoge(123);

$str1 = "abc";
$str2 = "HOGEHOGE";
$var1 = 123;
$var2 = 456;

$GLOBALS["Test Vars"] = array(
    "var1" => $var1,
    "var2" => &$var2,
    "str1" => $str1, 
    "str2" => &$str2,
    "hoge" => &$hoge,
);

ob_start();
?>

echo "a = [$a]\n";
echo "b = [$b]\n";

// create_functioのcodeとして扱う場合、当然関数の中になるので、下のextractは必須。
extract($GLOBALS['Test Vars']);
echo "=========================== 1\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
$str1 = "ABC";
$str2 = "foobar";
$var1 = 789;
$var2 = 654;
$hoge->_v = 456;
echo "=========================== 2\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
return array(
    "str_" => $str1 . $str2 . $a,
    "var_" => $var1 + $var2 + $b,
    "hoge" => $hoge,
);

<?php
$code = ob_get_contents();
ob_end_clean();

$f = create_function('$a, $b', $code);

$result = $f("hello", 999);
echo "-----------------------------\n";
var_dump($result);

echo "=========================== 3\n";
var_dump($str1, $str2, $var1, $var2, $hoge);
?>

実行してみます。

a = [hello]     ... ←関数に渡された引数
b = [999]
=========================== 1 ... 関数に入り、extract()された直後
string(3) "abc"
string(8) "HOGEHOGE"
int(123)
int(456)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(123)
}
=========================== 2 ... 関数の中で値を書き換えた直後
string(3) "ABC"
string(6) "foobar"
int(789)
int(654)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}
-----------------------------  ... 関数の戻り値のdump
array(3) {
  ["str_"]=>
  string(14) "ABCfoobarhello"
  ["var_"]=>
  int(2442)
  ["hoge"]=>
  object(Hoge)#1 (1) {
    ["_v"]=>
    int(456)
  }
}
=========================== 3 ... 関数の実行後、「外」の値
string(3) "abc"
string(8) "HOGEHOGE"
int(123)
int(456)
object(Hoge)#1 (1) {
  ["_v"]=>
  int(456)
}

関数の場合もextract()は必須になります。関数として実行される為、当然関数スコープに入りますのでグローバル変数は見えなくなります。
後は実験2と内容的には同一の結果になります。

というわけで、こんな案配で、PHPの疑似eval/lambdaのミニレシピの素案が出来上がってきたわけでした。