Closures - PHP vs Javascript
Sarfraz Ahmed April 22, 2015 02:17 PMIn 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.