Edit report at https://bugs.php.net/bug.php?id=50980&edit=1

 ID:                 50980
 Comment by:         paulomiguelmarques at gmail dot com
 Reported by:        nate at frickenate dot com
 Summary:            lambda/anonymous functions do not have proper scope
 Status:             Bogus
 Type:               Feature/Change Request
 Package:            Scripting Engine problem
 Operating System:   Linux
 PHP Version:        5.3.1
 Block user comment: N
 Private report:     N

 New Comment:

I guess it makes sense to be this way to be consistent with the rest of PHP, 
but it should definitely be properly documented on the web site, which it isn't.


Previous Comments:
------------------------------------------------------------------------
[2010-11-24 09:45:33] j...@php.net

see the last comments..

------------------------------------------------------------------------
[2010-08-28 14:58:49] + at ni-po dot com

function () use (&$country)

will fix it (as the previous comment already says). This is in concordance to 
the handling of arguments in normal functions and thus mustn't be changed.

------------------------------------------------------------------------
[2010-05-21 00:35:02] a at b dot c dot de

use() parameters are early binding - they use the variable's value at the point 
where the lambda function is declared, rather than the point where the lambda 
function is called (late binding).

If you want late binding then there is already syntax for that ('&') - which 
works as expected to propagate assignments up to the calling scope. To make it 
the default would be (a) contrary to the way "ordinary" function parameters are 
passed, (b) require the addition of new syntax to indicate that early binding 
is wanted, (c) deal with the situation where a used variable doesn't exist at 
call time, (d) make it hard to understand the difference between an ordinary 
function parameter and a closure.

The problem with allowing variables in the outer scope to leak by default into 
the lambda function is that a copy of the ENTIRE collection returned by 
get_defined_vars() would need to be persisted for the lifetime of the closure 
object (less any that are overridden by call-time arguments) just in case any 
of them are needed.

'I imagine this has to do with the php5 "fake references" that are used to pass 
objects around.'
The mechanism is the same as that used in Java and C#: the object reference is 
passed by value. It's just that in PHP you also have the option of passing the 
reference by reference as well (see the chapter in the manual on "Objects and 
references", where it explains that what is passed is an "object identifier" 
and not a PHP "reference").

------------------------------------------------------------------------
[2010-02-09 22:53:17] nate at frickenate dot com

Description:
------------
The way in which variables are passed to the inside of a lambda function via 
the 'use (...)' syntax is broken. The entire concept of "scope" or "closures" 
does not seem to apply in php whatsoever.

Variables within a lambda are supposed to be resolved/looked up in the parent 
scope at runtime when the variable is actually accessed within the lambda 
function's code. It seems that php just dumbly creates a copy of the variable 
when it sees the definition of the lambda function with the 'use (...)' clause. 
Changes to the variable in the parent scope between the moment where the lambda 
is defined and the lambda function is called are not propagated.

The worst part happens with objects. When passing in an object instance to a 
lambda, any changes to the original object in the parent scope after the lambda 
definition do affect the same object (problem 4 in reproduce code), but 
replacing the object altogether does not (problem 3 in reproduce code). I 
imagine this has to do with the php5 "fake references" that are used to pass 
objects around.

I get the feeling this is just a horrible limitation of the devs having hacked 
a "best-possible" implementation of "lambdas" into a Zend codebase that can't 
handle proper scope lookup. If this is the case, it would be nice if the 
documentation could be updated to really nail home the fact that there is no 
scoping going on at all, and that quite literally all you get is a copy of the 
variable as it exists at the moment the lambda is defined (except for the 
strangeness going on with objects).

It's unfortunate that the "solution" for the time being is to *always* pass in 
*every* variable as a reference, which results in expected output. But this 
requires creating a local copy within the lambda for every variable that one 
wants to modify without affecting the parent scope. :'(

Reproduce code:
---------------
<?php

// problem 1: this should echo "Canada", not a php notice
$fn = function () use ($country) { echo $country . "\n"; };
$country = 'Canada';
$fn();


// problem 2: this should echo "Canada", not "UnitedStates"
$country = 'UnitedStates';
$fn = function () use ($country) { echo $country . "\n"; };
$country = 'Canada';
$fn();


// problem 3: this should echo "Canada", not "UnitedStates"
$country = (object)array('name' => 'UnitedStates');
$fn = function () use ($country) { echo $country->name . "\n"; };
$country = (object)array('name' => 'Canada');
$fn();


// problem 4: this outputs "Canada". if this outputs "Canada",
// then so should problem 2 above. otherwise this should be
// just as broken as problem 2 and be outputting "UnitedStates"
$country = (object)array('name' => 'UnitedStates');
$fn = function () use ($country) { echo $country->name . "\n"; };
$country->name = 'Canada';
$fn();

?>

Expected result:
----------------
If scope was actually handled properly, then the lookup of the "use (...)"'d 
variable would occur at the moment the variable is used within the lambda, 
resulting in the following output:

Canada
Canada
Canada
Canada

Actual result:
--------------
PHP Notice:  Undefined variable: country in ... on line 5

UnitedStates
UnitedStates
Canada


------------------------------------------------------------------------



-- 
Edit this bug report at https://bugs.php.net/bug.php?id=50980&edit=1

Reply via email to