diff options
author | Dmitry Stogov <dmitry@php.net> | 2008-07-17 09:53:42 +0000 |
---|---|---|
committer | Dmitry Stogov <dmitry@php.net> | 2008-07-17 09:53:42 +0000 |
commit | 833a2295d143c67295dd94e20a56883b4f2d0787 (patch) | |
tree | ae1e5acb20381bbdda96ad813b8772f497329de5 | |
parent | 47e6c5d017c8e0451003f9eddcfa01db89857bd6 (diff) | |
download | php-git-833a2295d143c67295dd94e20a56883b4f2d0787.tar.gz |
Support for closures
-rwxr-xr-x | ext/curl/tests/curl_006.phpt | 35 | ||||
-rwxr-xr-x | ext/filter/tests/callback_closure.phpt | 14 | ||||
-rw-r--r-- | ext/mysqli/mysqli_api.c | 3 | ||||
-rwxr-xr-x | ext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt | 65 | ||||
-rwxr-xr-x | ext/pcntl/tests/signal_closure_handler.phpt | 25 | ||||
-rwxr-xr-x | ext/session/tests/save_handler_closures.inc | 9 | ||||
-rwxr-xr-x | ext/session/tests/session_set_save_handler_closures.phpt | 95 | ||||
-rwxr-xr-x | ext/sqlite/tests/sqlite_closures_001.phpt | 54 | ||||
-rwxr-xr-x | ext/sqlite/tests/sqlite_closures_002.phpt | 52 | ||||
-rw-r--r-- | ext/standard/basic_functions.c | 6 | ||||
-rwxr-xr-x | ext/standard/tests/assert/assert_closures.phpt | 16 | ||||
-rwxr-xr-x | ext/standard/tests/general_functions/closures_001.phpt | 11 | ||||
-rwxr-xr-x | ext/standard/tests/general_functions/closures_002.phpt | 25 | ||||
-rwxr-xr-x | ext/standard/tests/general_functions/ob_start_closures.phpt | 39 | ||||
-rwxr-xr-x | ext/xml/tests/xml_closures_001.phpt | 47 | ||||
-rw-r--r-- | ext/xml/xml.c | 2 | ||||
-rw-r--r-- | main/output.c | 13 |
17 files changed, 506 insertions, 5 deletions
diff --git a/ext/curl/tests/curl_006.phpt b/ext/curl/tests/curl_006.phpt new file mode 100755 index 0000000000..c4a6faa413 --- /dev/null +++ b/ext/curl/tests/curl_006.phpt @@ -0,0 +1,35 @@ +--TEST-- +Test curl_opt() function with CURLOPT_WRITEFUNCTION paremter set to a closure +--SKIPIF-- +<?php if (!extension_loaded("curl") || false === getenv('PHP_CURL_HTTP_REMOTE_SERVER')) print "skip"; ?> +--FILE-- +<?php +/* Prototype : bool curl_setopt(resource ch, int option, mixed value) + * Description: Set an option for a cURL transfer + * Source code: ext/curl/interface.c + * Alias to functions: + */ + + $host = getenv('PHP_CURL_HTTP_REMOTE_SERVER'); + + // start testing + echo '*** Testing curl_setopt($ch, CURLOPT_WRITEFUNCTION, <closure>); ***' . "\n"; + + $url = "{$host}/get.php?test=get"; + $ch = curl_init(); + + ob_start(); // start output buffering + curl_setopt($ch, CURLOPT_URL, $url); //set the url we want to use + curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) { + echo 'Data: '.$data; + return strlen ($data); + }); + + curl_exec($ch); + curl_close($ch); +?> +===DONE=== +--EXPECTF-- +*** Testing curl_setopt($ch, CURLOPT_WRITEFUNCTION, <closure>); *** +Data: Hello World! +Hello World!===DONE=== diff --git a/ext/filter/tests/callback_closure.phpt b/ext/filter/tests/callback_closure.phpt new file mode 100755 index 0000000000..e27a31b37e --- /dev/null +++ b/ext/filter/tests/callback_closure.phpt @@ -0,0 +1,14 @@ +--TEST-- +callback function is a closure +--SKIPIF-- +<?php if (!extension_loaded("filter")) die("skip"); ?> +--FILE-- +<?php +$callback = function ($var) { + return $var; +}; +$var = "test"; +var_dump(filter_var($var, FILTER_CALLBACK, array('options'=> $callback))); +?> +--EXPECT-- +string(4) "test" diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index a8c376cf46..b95584b554 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -1420,6 +1420,7 @@ PHP_FUNCTION(mysqli_set_local_infile_handler) efree(callback_name); RETURN_FALSE; } + efree(callback_name); /* save callback function */ if (!mysql->li_read) { @@ -1427,7 +1428,7 @@ PHP_FUNCTION(mysqli_set_local_infile_handler) } else { zval_dtor(mysql->li_read); } - ZVAL_STRING(mysql->li_read, callback_name, 0); + ZVAL_ZVAL(mysql->li_read, callback_func, 1, 0); RETURN_TRUE; } diff --git a/ext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt b/ext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt new file mode 100755 index 0000000000..3bdf6bb78b --- /dev/null +++ b/ext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt @@ -0,0 +1,65 @@ +--TEST-- +mysqli_set_local_infile_handler() - use closures as handler +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +if (!function_exists('mysqli_set_local_infile_handler')) + die("skip - function not available."); + +require_once('connect.inc'); +if (!$TEST_EXPERIMENTAL) + die("skip - experimental (= unsupported) feature"); + +if (!$link = mysqli_connect($host, $user, $passwb, $db, $port, $socket)) + die("skip Cannot connect to MySQL"); + +if (!$res = mysqli_query($link, 'SHOW VARIABLES LIKE "local_infile"')) { + mysqli_close($link); + die("skip Cannot check if Server variable 'local_infile' is set to 'ON'"); +} + +$row = mysqli_fetch_assoc($res); +mysqli_free_result($res); +mysqli_close($link); + +if ('ON' != $row['Value']) + die(sprintf("skip Server variable 'local_infile' seems not set to 'ON', found '%s'", + $row['Value'])); +?> +--INI-- +mysqli.allow_local_infile=1 +--FILE-- +<?php + require_once('connect.inc'); + require_once('local_infile_tools.inc'); + require_once('table.inc'); + + $callback_replace_buffer = function ($fp, &$buffer, $buflen, &$error) { + static $invocation = 0; + + printf("Callback: %d\n", $invocation++); + flush(); + + $buffer = fread($fp, $buflen); + + if ($invocation > 10) + return 0; + + return strlen($buffer); + }; + + $file = create_standard_csv(1); + if (!try_handler(20, $link, $file, $callback_replace_buffer, null)) + printf("[008] Failure\n"); + + mysqli_close($link); + print "done!"; +?> +--EXPECTF-- +Callback set to 'Closure object' +Callback: 0 +Callback: 1 +done!
\ No newline at end of file diff --git a/ext/pcntl/tests/signal_closure_handler.phpt b/ext/pcntl/tests/signal_closure_handler.phpt new file mode 100755 index 0000000000..ba9b8f5106 --- /dev/null +++ b/ext/pcntl/tests/signal_closure_handler.phpt @@ -0,0 +1,25 @@ +--TEST-- +Closures as a signal handler +--SKIPIF-- +<?php + if (!extension_loaded("pcntl")) print "skip"; + if (!function_exists("pcntl_signal")) print "skip pcntl_signal() not available"; + if (!function_exists("posix_kill")) print "skip posix_kill() not available"; + if (!function_exists("posix_getpid")) print "skip posix_getpid() not available"; +?> +--FILE-- +<?php +declare (ticks = 1); + +pcntl_signal(SIGTERM, function ($signo) { echo "Signal handler called!\n"; }); + +echo "Start!\n"; +posix_kill(posix_getpid(), SIGTERM); +$i = 0; // dummy +echo "Done!\n"; + +?> +--EXPECT-- +Start! +Signal handler called! +Done! diff --git a/ext/session/tests/save_handler_closures.inc b/ext/session/tests/save_handler_closures.inc new file mode 100755 index 0000000000..50f33c1098 --- /dev/null +++ b/ext/session/tests/save_handler_closures.inc @@ -0,0 +1,9 @@ +<?php + +require_once 'save_handler.inc'; + +foreach (array ('open', 'close', 'read', 'write', 'destroy', 'gc') as $fn) { + ${$fn.'_closure'} = function () use ($fn) { return call_user_func_array ($fn, func_get_args ()); }; +} + +?> diff --git a/ext/session/tests/session_set_save_handler_closures.phpt b/ext/session/tests/session_set_save_handler_closures.phpt new file mode 100755 index 0000000000..21b2c68737 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_closures.phpt @@ -0,0 +1,95 @@ +--TEST-- +Test session_set_save_handler() function : using closures as callbacks +--INI-- +session.save_path= +session.name=PHPSESSID +--SKIPIF-- +<?php include('skipif.inc'); ?> +--FILE-- +<?php + +ob_start(); + +/* + * Prototype : bool session_set_save_handler(callback $open, callback $close, callback $read, callback $write, callback $destroy, callback $gc) + * Description : Sets user-level session storage functions + * Source code : ext/session/session.c + */ + +echo "*** Testing session_set_save_handler() : using closures as callbacks ***\n"; + +require_once "save_handler_closures.inc"; +var_dump(session_module_name()); +var_dump(session_module_name(FALSE)); +var_dump(session_module_name("blah")); +var_dump(session_module_name("foo")); + +$path = dirname(__FILE__); +session_save_path($path); +session_set_save_handler($open_closure, $close_closure, $read_closure, $write_closure, $destroy_closure, $gc_closure); + +session_start(); +$_SESSION["Blah"] = "Hello World!"; +$_SESSION["Foo"] = FALSE; +$_SESSION["Guff"] = 1234567890; +var_dump($_SESSION); + +session_write_close(); +session_unset(); +var_dump($_SESSION); + +echo "Starting session again..!\n"; +session_id($session_id); +session_set_save_handler($open_closure, $close_closure, $read_closure, $write_closure, $destroy_closure, $gc_closure); +session_start(); +var_dump($_SESSION); +session_write_close(); + +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : using closures as callbacks *** + +string(%d) "%s" + +Warning: session_module_name(): Cannot find named PHP session module () in %s on line %d +bool(false) + +Warning: session_module_name(): Cannot find named PHP session module (blah) in %s on line %d +bool(false) + +Warning: session_module_name(): Cannot find named PHP session module (foo) in %s on line %d +bool(false) +Open [%s,PHPSESSID] +Read [%s,%s] +array(3) { + ["Blah"]=> + string(12) "Hello World!" + ["Foo"]=> + bool(false) + ["Guff"]=> + int(1234567890) +} +Write [%s,%s,Blah|s:12:"Hello World!";Foo|b:0;Guff|i:1234567890;] +Close [%s,PHPSESSID] +array(3) { + ["Blah"]=> + string(12) "Hello World!" + ["Foo"]=> + bool(false) + ["Guff"]=> + int(1234567890) +} +Starting session again..! +Open [%s,PHPSESSID] +Read [%s,%s] +array(3) { + ["Blah"]=> + string(12) "Hello World!" + ["Foo"]=> + bool(false) + ["Guff"]=> + int(1234567890) +} +Write [%s,%s,Blah|s:12:"Hello World!";Foo|b:0;Guff|i:1234567890;] +Close [%s,PHPSESSID] diff --git a/ext/sqlite/tests/sqlite_closures_001.phpt b/ext/sqlite/tests/sqlite_closures_001.phpt new file mode 100755 index 0000000000..8a90d1e5e0 --- /dev/null +++ b/ext/sqlite/tests/sqlite_closures_001.phpt @@ -0,0 +1,54 @@ +--TEST-- +sqlite: aggregate functions with closures +--INI-- +sqlite.assoc_case=0 +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded("sqlite")) print "skip"; ?> +--FILE-- +<?php +include "blankdb.inc"; + +$data = array( + "one", + "two", + "three" + ); + +sqlite_query("CREATE TABLE strings(a)", $db); + +foreach ($data as $str) { + sqlite_query("INSERT INTO strings VALUES('" . sqlite_escape_string($str) . "')", $db); +} + +function cat_step(&$context, $string) +{ + $context .= $string; +} + +function cat_fin(&$context) +{ + return $context; +} + +sqlite_create_aggregate($db, "cat", function (&$context, $string) { + $context .= $string; +}, function (&$context) { + return $context; +}); + +$r = sqlite_query("SELECT cat(a) from strings", $db); +while ($row = sqlite_fetch_array($r, SQLITE_NUM)) { + var_dump($row); +} + +sqlite_close($db); + +echo "DONE!\n"; +?> +--EXPECT-- +array(1) { + [0]=> + string(11) "onetwothree" +} +DONE! diff --git a/ext/sqlite/tests/sqlite_closures_002.phpt b/ext/sqlite/tests/sqlite_closures_002.phpt new file mode 100755 index 0000000000..e880bb1733 --- /dev/null +++ b/ext/sqlite/tests/sqlite_closures_002.phpt @@ -0,0 +1,52 @@ +--TEST-- +sqlite: regular functions with closures +--INI-- +sqlite.assoc_case=0 +--SKIPIF-- +<?php # vim:ft=php +if (!extension_loaded("sqlite")) print "skip"; ?> +--FILE-- +<?php +include "blankdb.inc"; + +$data = array( + array("one", "uno"), + array("two", "dos"), + array("three", "tres"), + ); + +sqlite_query("CREATE TABLE strings(a,b)", $db); + +foreach ($data as $row) { + sqlite_query("INSERT INTO strings VALUES('" . sqlite_escape_string($row[0]) . "','" . sqlite_escape_string($row[1]) . "')", $db); +} + +sqlite_create_function($db, "implode", function () { + $args = func_get_args(); + $sep = array_shift($args); + return implode($sep, $args); +}); + +$r = sqlite_query("SELECT implode('-', a, b) from strings", $db); +while ($row = sqlite_fetch_array($r, SQLITE_NUM)) { + var_dump($row); +} + +sqlite_close($db); + +echo "DONE!\n"; +?> +--EXPECT-- +array(1) { + [0]=> + string(7) "one-uno" +} +array(1) { + [0]=> + string(7) "two-dos" +} +array(1) { + [0]=> + string(10) "three-tres" +} +DONE! diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index fdddcb1b63..afa41e7209 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -5392,6 +5392,10 @@ static int user_tick_function_compare(user_tick_function_entry * tick_fe1, user_ zval result; zend_compare_arrays(&result, func1, func2 TSRMLS_CC); ret = (Z_LVAL(result) == 0); + } else if (Z_TYPE_P(func1) == IS_OBJECT && Z_TYPE_P(func2) == IS_OBJECT) { + zval result; + zend_compare_objects(&result, func1, func2 TSRMLS_CC); + ret = (Z_LVAL(result) == 0); } else { ret = 0; } @@ -6044,7 +6048,7 @@ PHP_FUNCTION(register_tick_function) efree(function_name); } - if (Z_TYPE_P(tick_fe.arguments[0]) != IS_ARRAY) { + if (Z_TYPE_P(tick_fe.arguments[0]) != IS_ARRAY && Z_TYPE_P(tick_fe.arguments[0]) != IS_OBJECT) { convert_to_string_ex(&tick_fe.arguments[0]); } diff --git a/ext/standard/tests/assert/assert_closures.phpt b/ext/standard/tests/assert/assert_closures.phpt new file mode 100755 index 0000000000..e01c11ace9 --- /dev/null +++ b/ext/standard/tests/assert/assert_closures.phpt @@ -0,0 +1,16 @@ +--TEST-- +assert() - basic - accept closures as callback. +--INI-- +assert.active = 1 +assert.warning = 1 +assert.bail = 0 +assert.quiet_eval = 0 +--FILE-- +<?php +assert_options(ASSERT_CALLBACK, function () { echo "Hello World!\n"; }); +assert(0); +?> +--EXPECTF-- +Hello World! + +Warning: assert(): Assertion failed in %s on line %d diff --git a/ext/standard/tests/general_functions/closures_001.phpt b/ext/standard/tests/general_functions/closures_001.phpt new file mode 100755 index 0000000000..b4fc898fd2 --- /dev/null +++ b/ext/standard/tests/general_functions/closures_001.phpt @@ -0,0 +1,11 @@ +--TEST-- +register_shutdown_function() & closure +--FILE-- +<?php +register_shutdown_function(function () { echo "Hello World!\n"; }); + +echo "Done\n"; +?> +--EXPECTF-- +Done +Hello World! diff --git a/ext/standard/tests/general_functions/closures_002.phpt b/ext/standard/tests/general_functions/closures_002.phpt new file mode 100755 index 0000000000..6df389bbb1 --- /dev/null +++ b/ext/standard/tests/general_functions/closures_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +register_tick_function() & closure +--FILE-- +<?php + +declare (ticks = 1); + +$i = 0; +register_tick_function(function () use (&$i) { $i++; }); + +echo "Test\n"; +echo "$i\n"; +echo "$i\n"; +var_dump ($i != 0); +echo "$i\n"; +echo "Done\n"; + +?> +--EXPECTF-- +Test +%d +%d +bool(true) +%d +Done diff --git a/ext/standard/tests/general_functions/ob_start_closures.phpt b/ext/standard/tests/general_functions/ob_start_closures.phpt new file mode 100755 index 0000000000..ba730961bd --- /dev/null +++ b/ext/standard/tests/general_functions/ob_start_closures.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ob_start() function : closures as output handlers +--INI-- +output_buffering=0 +--FILE-- +<?php +echo "*** Testing ob_start() : closures as output handlers ***\n"; + +ob_start(function ($output) { + return 'Output (1): ' . $output; +}); + +ob_start(function ($output) { + return 'Output (2): ' . $output; +}); + +echo "Test\nWith newlines\n"; + +$str1 = ob_get_contents (); + +ob_end_flush(); + +$str2 = ob_get_contents (); + +ob_end_flush(); + +echo $str1, $str2; + +?> +===DONE=== +--EXPECT-- +*** Testing ob_start() : closures as output handlers *** +Output (1): Output (2): Test +With newlines +Test +With newlines +Output (2): Test +With newlines +===DONE=== diff --git a/ext/xml/tests/xml_closures_001.phpt b/ext/xml/tests/xml_closures_001.phpt new file mode 100755 index 0000000000..5439a2a918 --- /dev/null +++ b/ext/xml/tests/xml_closures_001.phpt @@ -0,0 +1,47 @@ +--TEST-- +XML parser test using closures as callbacks +--SKIPIF-- +<?php include("skipif.inc"); ?> +--INI-- +magic_quotes_runtime=0 +--FILE-- +<?php +chdir(dirname(__FILE__)); + +$start_element = function ($xp, $elem, $attribs) +{ + print "<$elem"; + if (sizeof($attribs)) { + while (list($k, $v) = each($attribs)) { + print " $k=\"$v\""; + } + } + print ">\n"; +}; + +$end_element = function ($xp, $elem) +{ + print "</$elem>\n"; +}; + +$xp = xml_parser_create(); +xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); +xml_set_element_handler($xp, $start_element, $end_element); +$fp = fopen("xmltest.xml", "r"); +while ($data = fread($fp, 4096)) { + xml_parse($xp, $data, feof($fp)); +} +xml_parser_free($xp); + +?> +--EXPECT-- +<root id="elem1"> +<elem1> +<elem2> +<elem3> +<elem4> +</elem4> +</elem3> +</elem2> +</elem1> +</root> diff --git a/ext/xml/xml.c b/ext/xml/xml.c index b276f8d892..6f8ff34210 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -508,7 +508,7 @@ static void xml_set_handler(zval **handler, zval **data) } /* IS_ARRAY might indicate that we're using array($obj, 'method') syntax */ - if (Z_TYPE_PP(data) != IS_ARRAY) { + if (Z_TYPE_PP(data) != IS_ARRAY && Z_TYPE_PP(data) != IS_OBJECT) { convert_to_string_ex(data); if (Z_STRLEN_PP(data) == 0) { diff --git a/main/output.c b/main/output.c index 295dcfae01..92145afdc8 100644 --- a/main/output.c +++ b/main/output.c @@ -526,8 +526,17 @@ static int php_ob_init(uint initial_size, uint block_size, zval *output_handler, } } } else if (output_handler && output_handler->type == IS_OBJECT) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "No method name given: use ob_start(array($object,'method')) to specify instance $object and the name of a method of class %s to use as output handler", Z_OBJCE_P(output_handler)->name); - result = FAILURE; + /* do we have callable object */ + if (zend_is_callable(output_handler, 0, &handler_name)) { + SEPARATE_ZVAL(&output_handler); + Z_ADDREF_P(output_handler); + result = php_ob_init_named(initial_size, block_size, handler_name, output_handler, chunk_size, erase TSRMLS_CC); + efree(handler_name); + } else { + efree(handler_name); + php_error_docref(NULL TSRMLS_CC, E_ERROR, "No method name given: use ob_start(array($object,'method')) to specify instance $object and the name of a method of class %s to use as output handler", Z_OBJCE_P(output_handler)->name); + result = FAILURE; + } } else { result = php_ob_init_named(initial_size, block_size, OB_DEFAULT_HANDLER_NAME, NULL, chunk_size, erase TSRMLS_CC); } |