Question

I know that directly setting a variable in the scope of caller is probably not a good idea. However, the PHP extract() function does exactly that! I would like to write my own version of extract() but cannot figure out how to actually go about setting the variables in the caller. Any ideas? The closest I have come is modifying the caller's args using debug_backtrace(), but this is not exactly the same thing...

Was it helpful?

Solution

You can't modify local variables in a parent scope - the method which extract() uses is not exposed by PHP.

Also, what you get back from debug_stacktrace() isn't magically linked to the real stack. You can't modify it and hope your modifications are live!

OTHER TIPS

You could only do it in a PHP extension. If you call an internal PHP function, it will not run in a new PHP scope (i.e., no new symbol table will be created). Therefore, you can modify the "parent scope" by changing the global EG(active_symbol_table).

Basically, the core of the function would do something like extract does, the core of which is:

if (!EG(active_symbol_table)) {
    zend_rebuild_symbol_table(TSRMLS_C);
}
//loop through the given array
ZEND_SET_SYMBOL_WITH_LENGTH(EG(active_symbol_table),
    Z_STRVAL(final_name), Z_STRLEN(final_name) + 1, data, 1, 0);

There are, however, a few nuances. See the implementation of extract, but keep in mind a function that did what you wanted wouldn't need to be as complex; most of the code in extract is there to deal with the several options it accepts.

You can abuse the $GLOBALS scope to read and write variables from the caller of your function. See below sample function, which reads and write variables from the caller scope.

And yes, I know its dirty to abuse the $GLOBAL scope, but hey, we're here to fix problems ain't we? :)

function set_first_name($firstname) {
    /* check if $firstname is defined in caller */
    if(array_key_exists('firstname', $GLOBALS)) {
        $firstname_was = $GLOBALS['firstname'];
    } else {
        $firstname_was = 'undefined';
    }   

    /* set $firstname in caller */
    $GLOBALS['firstname'] = $firstname;

    /* show onscreen confirmation for debugging */
    echo '<br>firstname was ' . $firstname_was . ' and now is: ' . $firstname;
}  

set_first_name('John');

set_first_name('Michael');

The function returns the following output:

    <br>firstname was undefined and now is: John
    <br>firstname was John and now is: Michael

It depends on how badly you need to do this. If it's only for source beauty, find another way. If, for some reason, you really need to mess with parent scope, there's always a way.

SOLUTION 1

The safest method would be to actually use extract itself for this job, since it knows the trick. Say you want to make a function that extracts elements of an array but with all the names backwards - pretty weird! -, let's do this with a simple array-to-array transformation:

function backwardNames($x) {
    $out = [];
    foreach($x as $key=>$val) {
        $rev = strrev($key);
        $out[$rev] = $val;
    }
    return $out;
}

extract(backwardNames($myArray));

No magic here.

SOLUTION 2

If you need more than what extract does, use eval and var_export. YES I KNOW I KNOW everybody calm down please. No, eval is not evil. Eval is a power tool and it can be dangerous if you use it without care - so use it with care. (There is no way to go wrong if you only eval something that's been generated by var_export - it doesn't give any way to intrusions even if you put values in your array from an untrusted source. Array elements behave well.)

function makeMyVariables() {
    $vars = [
        "a" => 4,
        "b" => 5,
    ];
    $out = var_export($vars,1);
    $out = "extract(".$out.");";
    return $out;
}

eval(makeMyVariables());   // this is how you call it
// now $a is 4, $b is 5

This is almost the same, except that you can do a lot more in eval. And it's significantly slower, of course.

However, indeed, there is no way to do it with a single call.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top