Ради интереса я решил сделать механизм замыканий (closures) на PHP. Я знаю, что в PHP 5.3 такой механизм есть, поэтому подчёркиваю — чисто из академического интереса. И моя любознательность дала (по крайней мере, для меня — кто-то с этим уже мог иметь дело) свои плоды — помимо собственно замыканий я получил интересный метод передачи аргументов.
Механизм замыканий состоит из вот такого класса:
и не из чего больше. Вот так это выглядит в действии:
Метод closure::create внешне выглядит так же, как и create_function — только в начале ему передаётся ещё один параметр, окружение в виде ассоциативного массива, ключами которого являются имена переменных (без знака $), а значениями — соответственно их значения.
closure::bind принимает окружение в виде массива со значениями переменных и функцию, котороая будет замкнута на окуржение. Функция должна принимать в начале переменные окружения, и лишь затем аргументы, с которыми её будут вызывать.
print «create new»; и print «exists»; введены, чтобы показать, что каждое замыкание создаётся только один раз.
Пока PHP 5.3 не осел ещё плотно на всех хостингах, можно использовать данный мехнизм.
Теперь обратите внимание на строчки 37-39. Для наглядности я напишу отдельный пример:
На выходе из функции f значение переменной $a будет, как и было, 20 — функция func_get_args возвращает не массив аргументов, а массив значений аргументов, т.е. ссылка пропала и мы изменили локальную переменную.
Теперь рассмотрим вот такой пример:
Пример выведет 10, т.е. то, чего мы ожидали! В debug_backtrace хранятся именно сами аргументы. call_user_func_array умеет передавать значения по ссылке, так что
так же запишет в $a значение 10.
Я вполне допускаю, что для кого-то это окажется бояном, но, принимая во внимание то, что я этого раньше не знал и в интернете на эту тему найти ничего не мог, это может пригодиться и другим — потому не будьте слишком строги.
Благодарю за внимание!
UPD#1 05.12.09
Метод scope класса closure нужно изменить так:
Когда ссылка на переменную остаётся единственной, PHP её зачем-то преобразует в само значение, т.е. локальные переменные уже будут передаваться из окружения не по ссылке, а по значению, что бесполезно. С такой же модификацией пример
выведет для $local_var 71 и 72 соответственно, тогда как без указанного изменения метода он вывел бы в обоих случаях 71.
UPD#2 05.12.09
В сточке 49 класса нужно писать return call_user_func_array.
Механизм замыканий состоит из вот такого класса:
- class closure
- {
- private static $l_closures = array();
-
- public static function create($_scope, $_args, $_code)
- {
- $id = md5("closure::create>".serialize($_scope).$_args.$_code);
- if(isset(self::$l_closures[$id]))
- {
- print "exists<br>";
- return self::$l_closures[$id]["function"];
- }
- print "create new<br>";
- $header_code = '$scope=closure::scope("'.$id.'");';
- $header_end = 'unset($scope);';
- if($_scope)
- {
- foreach($_scope as $key => $value)
- {
- if($key == 'scope')
- {
- $header_end = '$'.$key.'=&$scope["'.$key.'"];';
- }
- else
- {
- $header_code .= '$'.$key.'=&$scope["'.$key.'"];';
- }
- }
- }
- $header_code .= $header_end;
- $closure = array(
- "function" => create_function($_args, $header_code.$_code),
- "scope" => is_array($_scope) ? $_scope : array()
- );
- self::$l_closures[$id] = $closure;
- return $closure["function"];
- }
-
- public static function bind($_scope, $_function)
- {
- $id = md5("closure::bind>".serialize($_scope).$_function);
- if(isset(self::$l_closures[$id]))
- {
- print "exists<br>";
- return self::$l_closures[$id]["function"];
- }
- print "create new<br>";
- $closure = array(
- "function" => create_function('', '$scope=closure::scope("'.$id.'");$debug=debug_backtrace();$args=$debug[0]["args"];call_user_func_array("'.$_function.'", array_merge($scope,$args));'),
- "scope" => is_array($_scope) ? $_scope : array()
- );
- self::$l_closures[$id] = $closure;
- return $closure["function"];
- }
-
- public static function scope($_id)
- {
- if(isset(self::$l_closures[$_id]))
- {
- return self::$l_closures[$_id]["scope"];
- }
- return array();
- }
- }
* This source code was highlighted with Source Code Highlighter.
и не из чего больше. Вот так это выглядит в действии:
- function outer1($_arg)
- {
- $local_var = 70;
- return closure::create(
- /* scope */ array('_arg' => &$_arg, 'local_var' => &$local_var),
- /* args */ '&$_inner_arg',
- /* code */ 'print $_arg." ".$local_var." ".$_inner_arg; $_arg = 110; $_inner_arg = 120;'
- );
- }
-
- $a = 50;
- $inner1 = outer1(&$a);
- $inner1 = outer1(&$a);
- $inner1 = outer1(&$a);
- $ia = 90;
- $inner1(&$ia);
- print " ".$a." ".$ia."<br>";
-
- function inner2($_arg, $local_var, &$_inner_arg)
- {
- print $_arg." ".$local_var." ".$_inner_arg; $_arg = 110; $_inner_arg = 120;
- }
-
- function outer2($_arg)
- {
- $local_var = 70;
- return closure::bind(
- /* scope */ array(&$_arg, &$local_var),
- /* function */ 'inner2'
- );
- }
-
- $a = 50;
- $inner2 = outer2(&$a);
- $inner2 = outer2(&$a);
- $inner2 = outer2(&$a);
- $ia = 90;
- $inner2(&$ia);
- print " ".$a." ".$ia."<br>";
* This source code was highlighted with Source Code Highlighter.
Метод closure::create внешне выглядит так же, как и create_function — только в начале ему передаётся ещё один параметр, окружение в виде ассоциативного массива, ключами которого являются имена переменных (без знака $), а значениями — соответственно их значения.
closure::bind принимает окружение в виде массива со значениями переменных и функцию, котороая будет замкнута на окуржение. Функция должна принимать в начале переменные окружения, и лишь затем аргументы, с которыми её будут вызывать.
print «create new»; и print «exists»; введены, чтобы показать, что каждое замыкание создаётся только один раз.
Пока PHP 5.3 не осел ещё плотно на всех хостингах, можно использовать данный мехнизм.
Теперь обратите внимание на строчки 37-39. Для наглядности я напишу отдельный пример:
- function f()
- {
- $args = func_get_args();
- $args[0] = 10;
- }
- $a = 20;
- f(&$a);
- print $a."<br>";
- $a = 20;
- call_user_func_array('f', array(&$a));
- print $a."<br>";
* This source code was highlighted with Source Code Highlighter.
На выходе из функции f значение переменной $a будет, как и было, 20 — функция func_get_args возвращает не массив аргументов, а массив значений аргументов, т.е. ссылка пропала и мы изменили локальную переменную.
Теперь рассмотрим вот такой пример:
- function f()
- {
- $debug = debug_backtrace();
- $debug[0]["args"][0] = 10;
- }
- $a = 20;
- f(&$a);
- print $a."<br>";
- $a = 20;
- call_user_func_array('f', array(&$a));
- print $a."<br>";
* This source code was highlighted with Source Code Highlighter.
Пример выведет 10, т.е. то, чего мы ожидали! В debug_backtrace хранятся именно сами аргументы. call_user_func_array умеет передавать значения по ссылке, так что
- call_user_func_array('f', array(&$a));
* This source code was highlighted with Source Code Highlighter.
так же запишет в $a значение 10.
Я вполне допускаю, что для кого-то это окажется бояном, но, принимая во внимание то, что я этого раньше не знал и в интернете на эту тему найти ничего не мог, это может пригодиться и другим — потому не будьте слишком строги.
Благодарю за внимание!
UPD#1 05.12.09
Метод scope класса closure нужно изменить так:
- public static function scope($_id)
- {
- $result = array();
- if(isset(self::$l_closures[$_id]))
- {
- if(self::$l_closures[$_id]["scope"])
- {
- foreach(self::$l_closures[$_id]["scope"] as $key => $value)
- {
- $result[$key] = &self::$l_closures[$_id]["scope"][$key];
- }
- }
- }
- return $result;
- }
* This source code was highlighted with Source Code Highlighter.
Когда ссылка на переменную остаётся единственной, PHP её зачем-то преобразует в само значение, т.е. локальные переменные уже будут передаваться из окружения не по ссылке, а по значению, что бесполезно. С такой же модификацией пример
- function inner2($_arg, $local_var, &$_inner_arg)
- {
- $local_var ++;
- print $_arg." ".$local_var." ".$_inner_arg; $_arg = 110; $_inner_arg = 120;
- }
-
- function outer2($_arg)
- {
- $local_var = 70;
- return closure::bind(
- /* scope */ array(&$_arg, &$local_var),
- /* function */ 'inner2'
- );
- }
-
- $a = 50;
- $inner2 = outer2(&$a);
- $inner2 = outer2(&$a);
- $inner2 = outer2(&$a);
- $ia = 90;
- $inner2(&$ia);
- print " ".$a." ".$ia."<br>";
- $inner2(&$ia);
- print " ".$a." ".$ia."<br>";
* This source code was highlighted with Source Code Highlighter.
выведет для $local_var 71 и 72 соответственно, тогда как без указанного изменения метода он вывел бы в обоих случаях 71.
UPD#2 05.12.09
В сточке 49 класса нужно писать return call_user_func_array.