Closures - PHP vs Javascript

Sarfraz Ahmed    April 22, 2015 02:17 PM

In my previous post Exploring Lambda Functions and Closures in PHP, I gave you example of function that can calculate factorial of given number:

$factorial = function( $n ) use ( &$factorial ) {
    if( $n == 1 ) return 1;
    return $factorial( $n - 1 ) * $n;
};

echo $factorial( 5 ); // 120

In that post I simply said it is important to pass $factorial by reference otherwise it won't work. Let's explore why it is so in this post.

Closures are pretty common in Javascript so let's create a simple closure in it and see how it behaves:

var addOne = function(num) {
    return function() { 
        console.log(num++); 
    };
};

var f = addOne(1);

f(); // 1
f(); // 2
f(); // 3

As can be seen, each time we call the function, it increments the number. Let's now convert the same in PHP:

$addOne = function($num) {
    return function() { 
        echo $num++; 
    };
};

$f = $addOne(1);

$f();
$f();
$f();

This would result in error:

Notice: Undefined variable: num

Why is it so ? Because variable scope is different in PHP and Javascript. In PHP, all variables go in local scope by default unless you explicitly declare them in global scope. However in Javascript all variables go in global scope unless you explicitly declare them in local scope using var keyword. Since we are talking about functions and closures here, we can say that in PHP a variable declared in a function can only be available to that function where it is declared. On the other hand, in Javascript a variable can not only be available to function where it is declared but also to inner functions of that parent function. Let's see an example:

function test() {
  $variable = 'value';

  return function() {
    echo $variable;
  };
}

$f = test();
$f(); // Notice: Undefined variable: variable

So PHP gave error as inner function didn't had access to outer variable $variable. We could have made this variable available to inner function either by creating closure using use keyword or declaring that variable in global scope. Le't see same example in Javascript:

function test() {
  var variable = 'value';

  return function() {
    console.log(variable);
  };
}

var f = test();
f(); // value

In Javascript though, the inner function was able to get variable from outer scope eg parent function in this example.

This is reason why Javascript inner function shown above didn't give error whereas PHP's did. Therefore to have access to outer variable in PHP, we need to create a closure by using the use keyword. Of course in Javascript, you don't need something like use keyword because as I mentioned in Javascript a variable of a parent function is available to all its inner functions as well. So we need to use the use keyword in PHP due to difference of variable scoping in PHP and Javascript.

So let's fix that error we received earlier in PHP:

$addOne = function($num) {
    return function() use ($num) { 
        echo $num++; 
    };
};

$f = $addOne(1);

$f(); // 1
$f(); // 1
$f(); // 1

Now we have created the closure and error is gone and $num variable is now available to inner function but there is a problem, each time we call the function it says 1 unlike Javascript example we saw earlier which said 1, 2 and 3. That's because unlike Javascript, PHP creates a new local variable inside inner function eg passes it by value not by reference. Conceptually, PHP sees this code like this:

$addOne = function($num) {
    return function() use ($num) { 
        $num = 1;  // so PHP kind of creates a new variable and assigns to 1 each time you call this
        echo $num++; 
    };
};

So to get similar output as in Javascript, we need to pass the variable using reference which can be done by using the & symbol:

$addOne = function($num) {
    return function() use (&$num) { // passed by reference 
        echo $num++; 
    };
};

$f = $addOne(1);

$f();
$f();
$f();

and now we get the output 1, 2, and 3.


And that's also reason why you needed to pass variable by reference for the factorial example I had given in my previous post. Hope this post clears the confusion of closures and scoping especially for someone who comes from Javascript to PHP.







Comments powered by Disqus