Is it possible to simulate closures in PHP 5.2.x not using globals?
Asked Answered
S

4

9

Is it possible to simulate closures in PHP 5.2.x not using globals? I could think of a way that would pass the desired variables as extra parameters to the closure but that just doesn't feel like best practice.

Any ideas?

Sybilla answered 5/2, 2010 at 18:12 Comment(3)
It is really sad that PHP does not support such an awesome construct.Morita
@Morita but it does from PHP5.3 upPerales
well, yes, but you have to list closed variables explicitly. "Closures on crutches" so to speak. Still better than nothing though.Buenrostro
P
8

Interesting question. I'd say it's not possible at all, but let's see

Quoting IBM - What's new in PHP5.3, Part 2

A closure is a function that is evaluated in its own environment, which has one or more bound variables that can be accessed when the function is called.

and further (emphasis mine)

Variables to be imported from the outside environment are specified in the use clause of the closure function definition. By default, they are passed by value, meaning that if we would update the value passed within the closure function definition, it would not update the outside value.

Using global would pass by reference and although it is possible to bind variables by reference with a closure by using & in the use clause, it is already a deviation from the 5.3 default behavior.

$var = 'yes';
$fn  = create_function('', 'global $var; $var = "no";');
$fn();
echo $var; // outputs no

You could copy the global variable in order to use it by value though, e.g.

$var = 'yes';
$fn  = create_function('', 'global $var; $tmp = $var; $tmp = "no";');
$fn();
echo $var; // outputs yes

In addition, the value of the global variable (when using create_function) will not be evaluated (bound) when the function is created but when the function is run

$var = 'yes';
$fn  = create_function('', 'global $var; $tmp = $var; return $tmp;');
$var = 'maybe';
echo $fn(); // outputs maybe

$var = 'yes';
$fn  = function() use ($var) { return $var; };
$var = 'maybe';
echo $fn(); // outputs yes

Also important is

When defined within an object, one handy thing is that the closure has full access to the object through the $this variable, without the need to import it explicitly. *Though I think this was dropped in final PHP5.3

This is impossible with the global keyword and you also cannot just use $this. There is no way to reference a property from a class when defining the function body with create_function.

class A {

    protected $prop = 'it works';

    public function test()
    {
        $fn = create_function('', 'echo $this->prop;');
        return $fn;
    }
}

$a = new A;
$fn = $a->test();
$fn();

will result in

Fatal error: Using $this when not in object context

To sum this up
While you can create a function importing a variable from the global scope, you cannot cannot create one using variables from another scope. And because you are technically not binding when using create_function but importing when the created function is executed, I'd like to argue this limitation makes the closure a lambda.


EDIT: The solution offered by Onno Marsman below is pretty decent though. It doesn't fully simulate Closures, but the implementation is pretty close.

Perales answered 5/2, 2010 at 18:54 Comment(2)
There are no words to describe how horrible this is. (Not your answer.)Morita
Great great answer! The detail with the global variable binding salvaged me from a lot of headache by the maintenance of a legacy project on php 5.2.17. Kudos!Entrance
I
3

My solution: http://techblog.triptic.nl/simulating-closures-in-php-versions-prior-to-php-5-3/

It does however pass the variables within an object to the closure as the first argument.

Inkster answered 31/3, 2010 at 21:32 Comment(3)
Very nice effort, though having to call call() technically disqualifies it as a Closure in my opinion (well, at least somewhat). Also I find having to pass in the Closure instance to get the closed over variables a bit awkward and call_user_func_array should return. But still, very nice. Did you model it after the Closure class in 5.3?Perales
I first thought of this solution before realizing there even existed an internal closure class in php 5.3. I don't like the call to call() myself and would use the __invoke magic method, only if it weren't available until php 5.3...Inkster
Gordon: What do mean with "call_user_func_array should return"?Inkster
S
2

Do you mean Currying like http://en.wikipedia.org/wiki/Currying

Then http://zaemis.blogspot.com/2009/06/currying-in-php.html

If not, never mind. :-)

Sorbian answered 5/2, 2010 at 18:39 Comment(1)
Not exactly what I meant but nice link anyway :DSybilla
T
1

There are some special cases where you can do it.

If you need to capture a variable by value (not by reference), and the value is a simple value type like a number, string, or array of the above (not reference types like objects and resources and functions), then you can simply insert it into the function definition using var_export():

$var = array(1, 3);
$f = create_function('',
    '$var=' . var_export($var,true) . '; return $var;');

If you need to capture the variable by reference in order to maintain mutable state across calls of the function, but you don't need to have the changes reflected in the original scope where it's created (e.g. to create an accumulator, but the changes to the sum don't need to change the sum variable in the creating scope), then you can similarly insert it, but as a static variable:

function make_accumulator($sum) {
    $f = create_function('$x',
        'static $var=' . var_export($var,true) . '; return $var += $x;');
    return $f;
}
Tetra answered 28/10, 2011 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.