Peregring-lk Peregring-lk - 15 days ago 5
PHP Question

Smarty assignByRef inside loop

I need to render some Smarty template for different values inside a loop (a PHP loop, not a Smarty

foreach
), in the following way (just an example):

$a = 0;
$b = 0;
$output = "";
$tmpl = "$a, $b";

$smarty->assignByRef('a', $a['a']);
$smarty->assignByRef('b', $b['b']);

for (int i = 0; i < 10; ++i) {
++$a;
++$b;

$output .= $smarty->fetch("string:" . $tmpl);
}


My doubt is about
assignByRef
. The Smarty v3 docs says:


With the introduction of PHP5, assignByRef() is not necessary for most
intents and purposes. assignByRef() is useful if you want a PHP array
index value to be affected by its reassignment from a template.
Assigned object properties behave this way by default.


but I don't fully understand what does that technical note means. So, can I use
assignByRef
that way or not? or using just
assign
will produce the same output?

Answer

PHP 4 objects were passed by value, unless the user explicitly specified the reference by prepending ampersand: &$variable. For this reason, function arguments that were likely to consume a big amount of memory were passed by reference in order to optimize memory usage:

function f(&$huge) {
  // ...
}

PHP 5 variables are passed by reference, even if the user did't specify it explicitly (the ampersand character is not used). By assigning one variable to another we only create a new container (internally called zval) for the same data in memory. Consider this:

$a = new stdClass;
$b = $a;

The first line allocates memory for variable $a and an object of stdClass, and stores the object's identifier into the variable. The second line allocates memory for variable $b, stores the object's identifier into the $b variable, and increments an internal reference counter. The reference counter value shows how many times the object is referenced in the code. When $b variable is destroyed, the reference counter is decremented by one. When the value of reference counter becomes equal to zero, the object's memory is deallocated. The following code demonstrates the idea:

$a = new stdClass;
debug_zval_dump($a);
$b = $a;
debug_zval_dump($a);
$c = $a;
debug_zval_dump($a);
$c = null; // destroy $c
debug_zval_dump($a);
$b = null; // destroy $b
debug_zval_dump($a);

Output

object(stdClass)#1 (0) refcount(2){
}
object(stdClass)#1 (0) refcount(3){
}
object(stdClass)#1 (0) refcount(4){
}
object(stdClass)#1 (0) refcount(3){
}
object(stdClass)#1 (0) refcount(2){
}

But when a variable is modified, PHP versions 5 and 7 create a copy of the variable in order to keep the original value (variable) intact.

$m1 = memory_get_usage();

$a = str_repeat('a', 1 << 24);
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 16,781,408

$b = $a;
$c = $a;
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 16,781,472

$b[0] = 'x';
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 33,562,880

$c[0] = 'x';
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 50,344,288

The same is applied to the context of the function arguments. Thus, if a variable is supposed to be used for read only, there is no need for passing it by reference explicitly. The words in the Smarty documentation mean that in most cases, you pass variables to the templates, and usually do not expect the template to change them. You need to pass a variable by reference only when you really want the variable to be modified in the template. The same concept is applied to any function arguments in PHP 5 and newer.