Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referencing variables from containing scope when using create_function as a closure. PHP

Using true closures, we can do,

function foo(&$ref)
{
    $inFn = function() use (&$ref)
    {   
        $ref = 42; 
    };  

    $inFn();
}

thus modifying the reference without having to pass it in the call to $inFn.

If we replace,

    $inFn = function(... 

with

    $inFn = create_function(...

is there any (simple and clean) way to do the same thing; refer to a variable of the containing scope by reference without explicitly passing it into $inFn?

like image 847
tjm Avatar asked Jun 26 '11 20:06

tjm


2 Answers

I came across this answer to another question which has inspired me to come up with the following. I haven't heavily tested it and I am sure it can be improved (and the need for the exec call is a shame), but it seems to solve my problem.

class create_closure
{
    private 
        $_cb  = null,
        $_use = array();

    public function __construct(array $use, $closure_args, $closure_body)
    {   
        $use_args = implode(array_keys($use), ',');

        $this->_cb = create_function(
            $use_args.($use_args==='' OR $closure_args==='' ? '' : ',').$closure_args,
            $closure_body
        );  

        $this->_use = array_values($use);

    }   

    public static function callback(array $use, $closure_args, $closure_body)
    {   
        $inst = new self($use, $closure_args, $closure_body);
        return array($inst, 'exec');
    }   

    public function exec()
    {   
        return call_user_func_array(
                $this->_cb,
                array_merge($this->_use, func_get_args())
        );  
    }   
}

You can use it like this:

function foo(&$ref)
{
    $inFn = new create_closure(
        array('$ref'=>&$ref), 
        '', 
        '$ref=42;'
    );
    $inFn->exec();
}

$x = 23;
echo 'Before, $x = ', $x, '<br>';
foo($x);
echo 'After,  $x = ', $x, '<br>';

Which returns:

Before, $x = 23
After, $x = 42

Or like this:

function bar()
{           
    $x = 0;
    echo 'x is ', $x, '<br>';

    $z = preg_replace_callback(
        '#,#',
        create_closure::callback(
            array('$x'=>&$x),
            '$matches',
            'return ++$x;
            '
        ),
        'a,b,c,d'
    );

    echo 'z is ', $z, '<br>';
    echo 'x is ', $x, '<br>';
}       

bar(); 

Which returns:

x is 0
z is a1b2c3d
x is 3
like image 147
tjm Avatar answered Nov 15 '22 14:11

tjm


is there any (simple and clean) way to do the same thing; refer to a variable of the containing scope by reference without explicitly passing it into $inFn?

Simple answer: no.

create_function is nothing more than a wrapper for eval that creates a pseudo-anonymous function and returns its name (i.e. 'lambda_1'). You cannot create closures with it (those are instances of the internal Closure class, a new language construct completely different from normal PHP functions. I guess that you are asking this, because you want to use closures* on an old PHP version. But since they did not exist before 5.3, you can't use them.

If you can live with a non-simple, non-clean solution, look at the other answers.

*) Maybe this could need a little clarification. Closure does not just mean anonymous function, they are exactly about your question: Memorizing the calling context. A normal function can not do this.

like image 31
Fabian Schmengler Avatar answered Nov 15 '22 13:11

Fabian Schmengler