]> granicus.if.org Git - php/commitdiff
Support for closures
authorDmitry Stogov <dmitry@php.net>
Thu, 17 Jul 2008 09:53:42 +0000 (09:53 +0000)
committerDmitry Stogov <dmitry@php.net>
Thu, 17 Jul 2008 09:53:42 +0000 (09:53 +0000)
17 files changed:
ext/curl/tests/curl_006.phpt [new file with mode: 0755]
ext/filter/tests/callback_closure.phpt [new file with mode: 0755]
ext/mysqli/mysqli_api.c
ext/mysqli/tests/mysqli_set_local_infile_handler_closures.phpt [new file with mode: 0755]
ext/pcntl/tests/signal_closure_handler.phpt [new file with mode: 0755]
ext/session/tests/save_handler_closures.inc [new file with mode: 0755]
ext/session/tests/session_set_save_handler_closures.phpt [new file with mode: 0755]
ext/sqlite/tests/sqlite_closures_001.phpt [new file with mode: 0755]
ext/sqlite/tests/sqlite_closures_002.phpt [new file with mode: 0755]
ext/standard/basic_functions.c
ext/standard/tests/assert/assert_closures.phpt [new file with mode: 0755]
ext/standard/tests/general_functions/closures_001.phpt [new file with mode: 0755]
ext/standard/tests/general_functions/closures_002.phpt [new file with mode: 0755]
ext/standard/tests/general_functions/ob_start_closures.phpt [new file with mode: 0755]
ext/xml/tests/xml_closures_001.phpt [new file with mode: 0755]
ext/xml/xml.c
main/output.c

diff --git a/ext/curl/tests/curl_006.phpt b/ext/curl/tests/curl_006.phpt
new file mode 100755 (executable)
index 0000000..c4a6faa
--- /dev/null
@@ -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 (executable)
index 0000000..e27a31b
--- /dev/null
@@ -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"
index a8c376cf46d0546828371ebc763558f8668a4432..b95584b554296892a35a58a4bd7d4d77721cabf2 100644 (file)
@@ -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 (executable)
index 0000000..3bdf6bb
--- /dev/null
@@ -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 (executable)
index 0000000..ba9b8f5
--- /dev/null
@@ -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 (executable)
index 0000000..50f33c1
--- /dev/null
@@ -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 (executable)
index 0000000..21b2c68
--- /dev/null
@@ -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 (executable)
index 0000000..8a90d1e
--- /dev/null
@@ -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 (executable)
index 0000000..e880bb1
--- /dev/null
@@ -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!
index fdddcb1b63d71c93cb7ec9f9addd9d69e5074202..afa41e72096318a0afd295d5a5f51f99c09905ff 100644 (file)
@@ -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 (executable)
index 0000000..e01c11a
--- /dev/null
@@ -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 (executable)
index 0000000..b4fc898
--- /dev/null
@@ -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 (executable)
index 0000000..6df389b
--- /dev/null
@@ -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 (executable)
index 0000000..ba73096
--- /dev/null
@@ -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 (executable)
index 0000000..5439a2a
--- /dev/null
@@ -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>
index b276f8d89227cc84a94fc86fc46d836299add9e6..6f8ff3421023ac10ec4aaaee20350f1b9c3baf5c 100644 (file)
@@ -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) {
index 295dcfae0185141c6e858685f0efec33b7d59683..92145afdc87650beb7bfa62f76fcb2e610100dc1 100644 (file)
@@ -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);
        }