skip to Main Content

I encountered strange behavior in callback function. Following is the code that should replace every letter of a string to it’s serial number while creating the map of replacements.

$i = 2;
$map = [];
$token = preg_replace_callback(
  '~w~',
  function($matches) use($map, $i) {
    $i += 1;
    $map[$i] = $matches[0];
    print "$i ";
    return $i;
  },
  '$token'
);
print_r($map);
print "n$token";

Weirdly it prints

3 3 3 3 3 Array
(
)

$33333

i.e. $i value is taken but it’s changing not used in next callback calls. What’s going on here? And how to fix it?

Sandbox below
https://onlinephp.io/c/a4b1c0

2

Answers


  1. Passing use parameters by reference, as in :

    $token = preg_replace_callback('~w~', function ($matches) use (&$map, &$i) {
            $i += 1;
            $map[$i] = $matches[0];
            print "$i ";
            
            return $i;
        }, '$token');
    

    Produces:

    3 4 5 6 7 Array
    (
    [3] => t
    [4] => o
    [5] => k
    [6] => e
    [7] => n
    )
    
    $34567
    

    You’d have to clarify your question to determine if this is what is desired besides the obvious value-vs-reference issue.

    Login or Signup to reply.
  2. By default variable capture in PHP is by value only – when the closure is created, the current value of each variable in the use list is examined, and assigned to a local variable with the same name inside the closure. Assigning to the local variable inside the closure does not write back to the variable which was originally captured, and every time you execute the closure, the captured variables start with their originally captured value.

    This is different from some other languages, like JavaScript, where closures carry writeable references to their captured variables. Capturing by value is generally easier to understand, for instance if you want to create a list of closures in a loop:

    $callbacks = [];
    foreach ( [1,3,5,7,9] as $number ) {
        $callbacks[] = function() use ($number) { return $number; };
    }
    

    If $number was captured by reference, all five closures would end up identical, all referring to the same variable; because it’s captured by value, you get five different closures, each with a different value of $number.

    In cases where you do want a reference, you can use the & modifier in the use list:

    function($matches) use(&$map, &$i) {
       // ...
    }
    

    Now, the local name $map inside the closure refers to the same variable as the name $map outside it – any modification to one will be visible in the other.

    This is similar to the difference between $myMap = $yourMap (assignment by value) and $myMap =& $yourMap (linking two variables as references).

    (Note that, in all the above cases, the "value" of an object is a pointer to something mutable – so if $map was an object rather than array, calling $map->setSomething(42) would modify the same object that was visible inside and outside the closure. This is not the same as capturing or assigning by reference.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search