Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:49207 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 1446 invoked from network); 5 Aug 2010 14:04:43 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 5 Aug 2010 14:04:43 -0000 Authentication-Results: pb1.pair.com header.from=glopes@nebm.ist.utl.pt; sender-id=unknown Authentication-Results: pb1.pair.com smtp.mail=glopes@nebm.ist.utl.pt; spf=permerror; sender-id=unknown Received-SPF: error (pb1.pair.com: domain nebm.ist.utl.pt from 193.136.128.21 cause and error) X-PHP-List-Original-Sender: glopes@nebm.ist.utl.pt X-Host-Fingerprint: 193.136.128.21 smtp1.ist.utl.pt Linux 2.6 Received: from [193.136.128.21] ([193.136.128.21:53529] helo=smtp1.ist.utl.pt) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id A5/E0-30659-9F4CA5C4 for ; Thu, 05 Aug 2010 10:04:42 -0400 Received: from localhost (localhost.localdomain [127.0.0.1]) by smtp1.ist.utl.pt (Postfix) with ESMTP id 5E357700E937 for ; Thu, 5 Aug 2010 15:04:38 +0100 (WEST) X-Virus-Scanned: by amavisd-new-2.6.4 (20090625) (Debian) at ist.utl.pt Received: from smtp1.ist.utl.pt ([127.0.0.1]) by localhost (smtp1.ist.utl.pt [127.0.0.1]) (amavisd-new, port 10025) with LMTP id X8JUeRKp1gAz for ; Thu, 5 Aug 2010 15:04:37 +0100 (WEST) Received: from mail2.ist.utl.pt (mail.ist.utl.pt [IPv6:2001:690:2100:1::8]) by smtp1.ist.utl.pt (Postfix) with ESMTP id 91B80700E841 for ; Thu, 5 Aug 2010 15:04:37 +0100 (WEST) Received: from cataphract-old.dulce.lo.geleia.net (52.152.108.93.rev.vodafone.pt [93.108.152.52]) (Authenticated sender: ist155741) by mail2.ist.utl.pt (Postfix) with ESMTPSA id 518D62008D23 for ; Thu, 5 Aug 2010 15:04:37 +0100 (WEST) Content-Type: multipart/mixed; boundary=----------6B816AtL0GKsKhpqvngnqP To: internals@lists.php.net References: Date: Thu, 05 Aug 2010 15:04:33 +0100 MIME-Version: 1.0 Organization: =?iso-8859-15?Q?N=FAcleo_de_Eng=2E_Biom=E9dica_?= =?iso-8859-15?Q?do_IST?= Message-ID: In-Reply-To: User-Agent: Opera Mail/10.60 (Win32) Subject: Re: [PHP-DEV] (now with PATCH) Closures, rebinding and scopes From: glopes@nebm.ist.utl.pt ("Gustavo Lopes") ------------6B816AtL0GKsKhpqvngnqP Content-Type: text/plain; charset=iso-8859-15; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit On Thu, 05 Aug 2010 14:54:21 +0100, Gustavo Lopes wrote: > Hi > > I've modified Closure::bindTo/Closure::bindTo so that they accept another > argument that defines the new scope. This can either be an object (its > class is used as the scope) or a class name. > [...] Sending renamed attachments. -- Gustavo Lopes ------------6B816AtL0GKsKhpqvngnqP Content-Disposition: attachment; filename=closure_039.txt Content-Type: text/plain; name=closure_039.txt Content-Transfer-Encoding: 7bit ---- EXPECTED OUTPUT int(-4) Testing with scope given as object int(21) int(22) Testing with scope as string int(23) int(24) Fatal error: Using $this when not in object context in %s on line %d ---- ACTUAL OUTPUT int(-4) Testing with scope given as object int(11) int(12) Testing with scope as string int(21) Fatal error: Function name must be a string in D:\Users\Cataphract\Documents\php-trunk\Zend\tests\closure_039.php on line 40 --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x029c17c0 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x029c17c0 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x029c17c0 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- ---- FAILED ------------6B816AtL0GKsKhpqvngnqP Content-Disposition: attachment; filename=closures_scope_patch.txt Content-Type: text/plain; name=closures_scope_patch.txt Content-Transfer-Encoding: 7bit Index: tests/closure_036.phpt =================================================================== --- tests/closure_036.phpt (revision 301876) +++ tests/closure_036.phpt (working copy) @@ -1,5 +1,5 @@ --TEST-- -Closure 036: Rebinding closures +Closure 036: Rebinding closures, keep calling scope --FILE-- getIncrementor(); $cb = $ca->bindTo($b); -$cb2 = Closure::bind($b, $ca); +$cb2 = Closure::bind($ca, $b); var_dump($ca()); var_dump($cb()); Index: tests/closure_038.phpt =================================================================== --- tests/closure_038.phpt (revision 0) +++ tests/closure_038.phpt (revision 0) @@ -0,0 +1,58 @@ +--TEST-- +Closure 038: Rebinding closures, change scope, different runtime type +--FILE-- +x = $v; + } + + public function getIncrementor() { + return function() { return ++$this->x; }; + } +} +class B extends A { + private $x; + public function __construct($v) { + parent::__construct($v); + $this->x = $v*2; + } +} + +$a = new A(0); +$b = new B(10); + +$ca = $a->getIncrementor(); +var_dump($ca()); + +echo "Testing with scope given as object", "\n"; + +$cb = $ca->bindTo($b, $b); +$cb2 = Closure::bind($ca, $b, $b); +var_dump($cb()); +var_dump($cb2()); + +echo "Testing with scope as string", "\n"; + +$cb = $ca->bindTo($b, 'B'); +$cb2 = Closure::bind($ca, $b, 'B'); +var_dump($cb()); +var_dump($cb2()); + +$cb = $ca->bindTo($b, NULL); +var_dump($cb()); + +?> +--EXPECTF-- +int(1) +Testing with scope given as object +int(21) +int(22) +Testing with scope as string +int(23) +int(24) + +Fatal error: Using $this when not in object context in %s on line %d \ No newline at end of file Index: tests/closure_039.phpt =================================================================== --- tests/closure_039.phpt (revision 0) +++ tests/closure_039.phpt (revision 0) @@ -0,0 +1,58 @@ +--TEST-- +Closure 039: Rebinding closures, change scope, same runtime type +--FILE-- +x = $v; + } + + public function getIncrementor() { + return function() { return ++$this->x; }; + } +} +class B extends A { + private $x; + public function __construct($v) { + parent::__construct($v); + $this->x = $v*2; + } +} + +$a = new B(-5); +$b = new B(10); + +$ca = $a->getIncrementor(); +var_dump($ca()); + +echo "Testing with scope given as object", "\n"; + +$cb = $ca->bindTo($b, $b); +$cb2 = Closure::bind($ca, $b, $b); +var_dump($cb()); +var_dump($cb2()); + +echo "Testing with scope as string", "\n"; + +$cb = $ca->bindTo($b, 'B'); +$cb2 = Closure::bind($ca, $b, 'B'); +var_dump($cb()); +var_dump($cb2()); + +$cb = $ca->bindTo($b, NULL); +var_dump($cb()); + +?> +--EXPECTF-- +int(-4) +Testing with scope given as object +int(21) +int(22) +Testing with scope as string +int(23) +int(24) + +Fatal error: Using $this when not in object context in %s on line %d \ No newline at end of file Index: tests/closure_040.phpt =================================================================== --- tests/closure_040.phpt (revision 0) +++ tests/closure_040.phpt (revision 0) @@ -0,0 +1,45 @@ +--TEST-- +Closure 040: Rebinding closures, bad arguments +--FILE-- +x = $v; + } + + public function getIncrementor() { + return function() { return ++$this->x; }; + } + public function getStaticIncrementor() { + return static function() { return ++static::$xs; }; + } +} + +$a = new A(20); + +$ca = $a->getIncrementor(); +$cas = $a->getStaticIncrementor(); + +$ca->bindTo($a, array()); +$ca->bindTo(array(), 'A'); +$ca->bindTo($a, array(), ""); +$ca->bindTo(); +$cas->bindTo($a, 'A'); + +?> +--EXPECTF-- +Notice: Array to string conversion in %s on line %d + +Warning: Class 'Array' not found in %s on line %d + +Warning: Closure::bindTo() expects parameter 1 to be object, array given in %s on line 25 + +Warning: Closure::bindTo() expects at most 2 parameters, 3 given in %s on line %d + +Warning: Closure::bindTo() expects at least 1 parameter, 0 given in %s on line %d + +Warning: Tried to bind an instance to a static closure in %s on line %d \ No newline at end of file Index: tests/closure_041.phpt =================================================================== --- tests/closure_041.phpt (revision 0) +++ tests/closure_041.phpt (revision 0) @@ -0,0 +1,40 @@ +--TEST-- +Closure 041: Closure->getScope() +--FILE-- +x = $v; + } + + public function getIncrementor() { + return function() { return ++$this->x; }; + } + public function getStaticIncrementor() { + return static function() { return ++static::$xs; }; + } +} + +$a = new A(20); + +$c = function() {}; +$ca = $a->getIncrementor(); +$cas = $a->getStaticIncrementor(); + +var_dump($c->getScope()); +var_dump($ca->getScope()); +var_dump($cas->getScope()); +var_dump($cas->getScope(array())); + +?> +--EXPECTF-- +NULL +string(1) "A" +string(1) "A" + +Warning: Closure::getScope() expects exactly 0 parameters, 1 given in %s on line %d +NULL \ No newline at end of file Index: zend_closures.c =================================================================== --- zend_closures.c (revision 301876) +++ zend_closures.c (working copy) @@ -76,39 +76,81 @@ } /* }}} */ -/* {{{ proto Closure Closure::bindTo(object $to) - Bind a closure to another object */ -ZEND_METHOD(Closure, bindTo) /* {{{ */ +/* {{{ proto Closure Closure::bind(Closure $old, object $to [, mixed $scope = $this->getScope() ] ) + Create a closure from another one and bind to another object and scope */ +ZEND_METHOD(Closure, bind) /* {{{ */ { - zval *newthis; - zend_closure *closure = (zend_closure *)zend_object_store_get_object(getThis() TSRMLS_CC); + zval *newthis, *zclosure, *scope_arg = NULL; + zend_closure *closure; + zend_class_entry *ce, **ce_p; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!", &newthis) == FAILURE) { + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oo!|z", &zclosure, zend_ce_closure, &newthis, &scope_arg) == FAILURE) { RETURN_NULL(); } - zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC); + closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC); + + if ((newthis != NULL) && (closure->func.common.fn_flags & ZEND_ACC_STATIC)) { + zend_error(E_WARNING, "Tried to bind an instance to a static closure"); + } + + if (scope_arg != NULL) { /* scope argument was given */ + if (IS_ZEND_STD_OBJECT(*scope_arg)) { + ce = Z_OBJCE_P(scope_arg); + } else if (Z_TYPE_P(scope_arg) == IS_NULL) { + ce = NULL; + } else { + char *class_name; + int class_name_len; + zval tmp_zval; + INIT_ZVAL(tmp_zval); + + if (Z_TYPE_P(scope_arg) == IS_STRING) { + class_name = Z_STRVAL_P(scope_arg); + class_name_len = Z_STRLEN_P(scope_arg); + } else { + tmp_zval = *scope_arg; + zval_copy_ctor(&tmp_zval); + convert_to_string(&tmp_zval); + class_name = Z_STRVAL(tmp_zval); + class_name_len = Z_STRLEN(tmp_zval); + } + + if (zend_lookup_class_ex(class_name, class_name_len, NULL, 1, &ce_p TSRMLS_CC) == FAILURE) { + zend_error(E_WARNING, "Class '%s' not found", class_name); + RETURN_NULL(); + } else { + ce = *ce_p; + } + zval_dtor(&tmp_zval); + } + } else { /* scope argument not given; do not change the scope by default */ + ce = closure->func.common.scope; + } + + zend_create_closure(return_value, &closure->func, ce, newthis TSRMLS_CC); } /* }}} */ -/* {{{ proto Closure Closure::bind(object $to, Closure $old) - Create a closure to with binding to another object */ -ZEND_METHOD(Closure, bind) /* {{{ */ +ZEND_METHOD(Closure, getScope) { - zval *newthis, *zclosure; - zend_closure *closure; + zend_closure *closure; + zend_class_entry *ce; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o!O", &newthis, &zclosure, zend_ce_closure) == FAILURE) { + if (zend_parse_parameters_none() == FAILURE) { RETURN_NULL(); } - closure = (zend_closure *)zend_object_store_get_object(zclosure TSRMLS_CC); + closure = (zend_closure *)zend_object_store_get_object(getThis() TSRMLS_CC); + ce = closure->func.common.scope; - zend_create_closure(return_value, &closure->func, newthis?Z_OBJCE_P(newthis):NULL, newthis TSRMLS_CC); + if (ce != NULL) { + RETURN_STRINGL(ce->name, ce->name_length, 1); + } else { + RETURN_NULL(); + } } -/* }}} */ - static zend_function *zend_closure_get_constructor(zval *object TSRMLS_DC) /* {{{ */ { zend_error(E_RECOVERABLE_ERROR, "Instantiation of 'Closure' is not allowed"); @@ -356,19 +398,25 @@ } /* }}} */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bindto, 0, 0, 1) ZEND_ARG_INFO(0, newthis) + ZEND_ARG_INFO(0, newscope) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_bind, 0, 0, 2) + ZEND_ARG_INFO(0, closure) ZEND_ARG_INFO(0, newthis) - ZEND_ARG_INFO(0, closure) + ZEND_ARG_INFO(0, newscope) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_closure_getscope, 0, 0, 0) +ZEND_END_ARG_INFO() + static const zend_function_entry closure_functions[] = { ZEND_ME(Closure, __construct, NULL, ZEND_ACC_PRIVATE) - ZEND_ME(Closure, bindTo, arginfo_closure_bindto, ZEND_ACC_PUBLIC) ZEND_ME(Closure, bind, arginfo_closure_bind, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_MALIAS(Closure, bindTo, bind, arginfo_closure_bindto, ZEND_ACC_PUBLIC) + ZEND_ME(Closure, getScope, arginfo_closure_getscope, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; ------------6B816AtL0GKsKhpqvngnqP Content-Disposition: attachment; filename=closure_038.txt Content-Type: text/plain; name=closure_038.txt Content-Transfer-Encoding: 7bit ---- EXPECTED OUTPUT int(1) Testing with scope given as object int(21) int(22) Testing with scope as string int(23) int(24) Fatal error: Using $this when not in object context in %s on line %d ---- ACTUAL OUTPUT int(1) Testing with scope given as object int(21) int(22) Testing with scope as string --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x02741730 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- int(23) int(24) --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x02741730 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- Fatal error: Using $this when not in object context in D:\Users\Cataphract\Documents\php-trunk\Zend\tests\closure_038.php on line 11 --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x02741730 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x02741730 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- --------------------------------------- d:\users\cataphract\documents\php-trunk\zend\zend_opcode.c(373) : Block 0x02741730 status: Beginning: Cached Freed (invalid) Start: OK End: OK --------------------------------------- ---- FAILED ------------6B816AtL0GKsKhpqvngnqP--