closureの最近のブログ記事



PHPでクロージャ


作ってみたものの、実はあんまり用途が思い浮かんでない。


なんとなく、 array_walk がキーワードに浮かんでは来るけど。



てことで、とりあえず smarty で使ってみようと思う。



CakePHP を完全 smarty 化してみたものの、


smarty 内での配列の扱いが微妙だ。


↓事の顛末はこの辺に。


http://d.hatena.ne.jp/am11op/20070614/1181837256


要は、php ならこう書くべきところを



<?php echo $html->link('Edit', array('action'=>'edit', 2));?>

smarty では view に変なメソッド作った上でこう書く必要があった。



{$html->link('Edit', $view->_array('action=>edit', 2))}

まあ、これでもよいんだけど、せっかくなんで closure (的なもの)を使ってみる。


Closure はこんなの。



class Closure {
var $_args;
var $_func;
/**
* @private
*/
function Closure($func=null, $args=null) {
if ($func) {
$this->_args = $args;
$func = ereg_replace("^function\([^\)]*\) *\{", "", $func);
$func = ereg_replace("\}$", "", $func);
$this->_func = $func;
}
}

/**
* @param $func string closure
* @param $arg1 $mixed first object/var which is used in $func
* @param $arg2 $mixed second object/var which is used in $func
* ...
*/
function bind() {
$args = func_get_args();
return new Closure(array_shift($args), $args);
}

/**
* use this method to call closure
*/
function call() {
$func = create_function('$args', $this->_func);
return $func($this->_args);
}

function func() {
$args = func_get_args();
$obj = new Closure(array_shift($args), $args);
return $obj->call();
}
}


無駄にインスタンス量産してるのは、書き直すのが面倒だったから。


このクラスを closure という名前で assign_by_ref してやって、


smarty 内からこう使う。



{*$html->link('Edit', $view->_array('action=>edit', 2))*}
{$html->link('Edit', $closure->func("return array('action'=>'edit', 2);"))}

文字数増えてる!


ネタです!


まあ、ちょっと変数を php で処理して何かのメソッドに渡したい時、


一回しか使わないような modifier をいちいち作らなくてよいので便利かも。


ただし、入力値に対して使う場合は任意のコードを実行されないように気をつけること(棒読み)。




http://d.hatena.ne.jp/am11op/20070702/1183376369


↑前エントリのクロージャもどきがものすごく使いたくならない理由がわかった。



function closure($str) {
	$str = ereg_replace("^function\(\) *\{", "", $str);
	$str = ereg_replace("\}$", "", $str);


$func =create_function('', $str);
$func();
}

class Test{
function sayTest(){
echo 'test';
}
}
$obj = new Test;

function piyo($bool, $obj='') {
if ($bool) {
closure($obj['onSuccess']);
} else {
closure($obj['onFailure']);
}
}

$arr1 = array(
'onSuccess'=>'echo "OK!";',
'onFailure'=>'echo "NG!";',
);

echo piyo(true, $arr1); // OK!
echo piyo(false, $arr1); // NG!

$arr2 = array(
'onSuccess'=>'function(){echo $obj->sayTest();}', // この時点で syntax error
);

echo piyo(true, $arr2);

class とか instance 使った時点で閉じた空間じゃなくなるので、


こういうものはいくらそれっぽく作っても、


結局 closure とは呼べないだろうと思った。



結論

失敗例を増やしただけ




※これ失敗でした。↓にもちょっとマシなやつ書いてます


http://d.hatena.ne.jp/am11op/20070702/1183392610


CakePHP 使ってたら、ものすごく closure 使いたくなった。


ググったら途中までやってる方達がいたので、


それパクって作ってみた。



↓途中までやってる方達


http://blog.xole.net/article.php?id=419


http://p0t.jp/mt/archives/2007/04/1byte.html


要は create_function を使えばよいわけだ。


closure.php



function closure($str) {
$str = ereg_replace("^function\(\) *\{", "", $str);
$str = ereg_replace("\}$", "", $str);


$func =create_function('', $str);
$func();
}

function hoge($str, $func='') {
echo $str;
if ($func) {
closure($func);
}
}

echo hoge('こんにちわ。', "function() {echo 'クロージャだよ!';}");


実行結果



こんにちわ。クロージャだよ!


ものすごく使う気にならないのはなんでだ。




http://d.hatena.ne.jp/am11op/20070702/1183376369


↑前エントリがただの劣化コピーだってことに電車の中で気が付いた。


てことで、勝手にリベンジ。



その前に、closure の定義自体はとりあえず置いておいて、


自分が作ろうとしてる(欲しい)のは何なのか。



  • 外側の変数を参照できる

  • 使うその場で定義できる



↓ていうか、これ。


http://itpro.nikkeibp.co.jp/article/COLUMN/20050930/221971/?ST=oss



これができないのは、ただの callback 関数なんじゃないかと思うわけです。



で作ったのがこれ。



/*
* closure を実現するための class
*/
class Closure {
var $_args;
var $_func;
/**
* @private
*/
function Closure($func, $args) {
$this->_args = $args;
$func = ereg_replace("^function\([^\)]*\) *\{", "", $func);
$func = ereg_replace("\}$", "", $func);
$this->_func = $func;
}

/**
* @param $func string closure
* @param $arg1 $mixed first object/var which is used in $func
* @param $arg2 $mixed second object/var which is used in $func
* ...
*/
function bind() {
$args = func_get_args();
return new Closure(array_shift($args), $args);
}

/**
* use this method to call closure
*/
function call() {
$func = create_function('$args', $this->_func);
$func($this->_args);
}


}

// 以下、使用例
class Test{
function sayTest($int=0){
return 'this is a test.';
}
}
$obj = new Test;

function piyo($bool, $obj) {
if ($bool) {
$obj['onSuccess']->call();
} else {
$obj['onFailure']->call();
}
}

$hoge = "you know, ";

$arr = array(
'onSuccess'=>Closure::bind("function(\$args){echo '$hoge'.\$args[0]->sayTest();}", &$obj),
'onFailure'=>Closure::bind("function(\$args){echo 'fail!';"),
);

echo piyo(true, $arr); // you know, this is a test.
echo piyo(false, $arr); // fail!



  • Closure::bind の第一引数に closure を渡す

    • closure 内の instance は全て $args とする



  • $args に対応する instance を第二引数以降に渡す

  • closure はダブルクォーテーションでくくる場合は、$args の前に「\」を付ける必要がある

  • closure の頭に「function(\$args){」、尻に「}」が付いているが、これは closure であることを明示するためである

    • どっちみち ereg_replace で削除されるので、はっきり言って必要ない





「\」がウザい場合は、クロージャをシングルクォーテーションで囲って、


普通の変数も引数に渡してクロージャ内から $args[n] として参照すればよいです。



ちょっとは closure ぽくなったでしょか。