summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Stogov <dmitry@php.net>2008-07-17 09:53:42 +0000
committerDmitry Stogov <dmitry@php.net>2008-07-17 09:53:42 +0000
commit833a2295d143c67295dd94e20a56883b4f2d0787 (patch)
treeae1e5acb20381bbdda96ad813b8772f497329de5
parent47e6c5d017c8e0451003f9eddcfa01db89857bd6 (diff)
downloadphp-git-833a2295d143c67295dd94e20a56883b4f2d0787.tar.gz
Support for closures
-rwxr-xr-xext/curl/tests/curl_006.phpt35
-rwxr-xr-xext/filter/tests/callback_closure.phpt14
-rw-r--r--ext/mysqli/mysqli_api.c3
-rwxr-xr-xext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt65
-rwxr-xr-xext/pcntl/tests/signal_closure_handler.phpt25
-rwxr-xr-xext/session/tests/save_handler_closures.inc9
-rwxr-xr-xext/session/tests/session_set_save_handler_closures.phpt95
-rwxr-xr-xext/sqlite/tests/sqlite_closures_001.phpt54
-rwxr-xr-xext/sqlite/tests/sqlite_closures_002.phpt52
-rw-r--r--ext/standard/basic_functions.c6
-rwxr-xr-xext/standard/tests/assert/assert_closures.phpt16
-rwxr-xr-xext/standard/tests/general_functions/closures_001.phpt11
-rwxr-xr-xext/standard/tests/general_functions/closures_002.phpt25
-rwxr-xr-xext/standard/tests/general_functions/ob_start_closures.phpt39
-rwxr-xr-xext/xml/tests/xml_closures_001.phpt47
-rw-r--r--ext/xml/xml.c2
-rw-r--r--main/output.c13
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);
}