OK so I'm a bit embarrassed to admit that I don't fully understand assigning values by reference in PHP. While I generally understand that when you pass a variable to a function that it makes a COPY of the parameters you pass, I found this behavior a bit surprising:

<?php

class foo {
	public $obj;

public function __construct() {
	$bar = new bar();

	$this->obj = $bar; // this is NOT assign by reference but seems to have the same effect?

	$bar->v1 = "new value";
}
}

class bar {
	public $v1 = "initial1";
	public $v2 = "initial2";
}

$f = new foo();
var_dump($f);

?>

I'm not assigning $this->obj by reference, but changing the value of $bar after changes $this->obj as well. While I remember from my C-coding days the idea that pointers are probably getting passed around so this kinda makes sense, but then I start to wonder that we are getting pointer-like actions without using assign-by-reference.

Are there any circumstances where one SHOULD use assign by reference? If I do expect the behavior above, should I possibly use assign-by-reference as a stylistic thing to make the expected behavior more explicit? Can anyone offer some anecdotes about using assign-by-reference?

    sneakyimp;11038405 wrote:

    OK so I'm a bit embarrassed to admit that I don't fully understand assigning values by reference in PHP. While I generally understand that when you pass a variable to a function that it makes a COPY of the parameters you pass, I found this behavior a bit surprising:

    $this->obj = $bar; // this is NOT assign by reference but seems to have the same effect?

    Objects are always assigned by reference, no matter what (if you want a copy instead, you need to use [man]clone[/man]).

    sneakyimp;11038405 wrote:

    Are there any circumstances where one SHOULD use assign by reference? If I do expect the behavior above, should I possibly use assign-by-reference as a stylistic thing to make the expected behavior more explicit? Can anyone offer some anecdotes about using assign-by-reference?

    Your analogy of "pointers" is a good one. In practical terms, think of it this way. If you're writing something like this:

    <?php
    
    $var = some_function( $var );

    …where the assignment (left side) seems redundant, then you could be using a reference. Be careful, though, because references (like objects) are impossible to de-reference: once you do it, you're stuck with it. I typically only use references inside of functions, where there's some useful things you can do with them (and the reference itself is limited to the function's scope, and so is less problematic later on). For example:

    <?php
    
    function referenced_message( $a,$b,$message ){
        $r = $a +$b;
        $message = ($a ==0 || $b == 0)?
            "one or both of the numbers were zero":
            "neither number was zero";
        return $r;
    }

    it's kinda like a footnote, or "extra" return parameter. [man]preg_match[/man] does this, for example, with its [font=monospace]$matches[/font] parameter.

    or

    <?php
    
    $function = function( $n )use( &$other_function ){
        return $other_function?
            $other_function( $n ) + $n:
            $n + $n;
    }
    $other_function = function( $n ){
        return $n / 2;
    }

    in this case, this allows the closure to use other closures that may or may not be defined (or will be defined later).

      I think back in PHP 4 days, there was some perceived performance gain to assigning objects by reference, but now that it's the default behavior, it's no longer necessary -- unless you need it to be a copy instead of a reference, in which case I believe [man]clone/man is the way to go? (I have yet to run into a situation where I needed to use clone() -- or am too ignorant to realize I had.)

        Assigning by reference can also be useful for arrays and foreach, but remember to unset after the loop! Overly simplified example follows, which doesn't really show its usefulness:

        $arr = [1,2,3];
        foreach( $arr as &$val )
        {
           $val+= $val;
           unset($val);
        }
        var_dump($arr);
        // array(3) { [0]=> int(2) [1]=> int(4) [2]=> int(6) }
        
          traq wrote:

          Objects are always assigned by reference, no matter what (if you want a copy instead, you need to use clone).

          This isn't really true. The problem here is that you've confused two different uses of the word "reference" - "reference variable" and "object reference", which is really the fault of PHP's developers for using the word "reference" in the former situation (though to be fair, the latter didn't exist in PHP until v5).

          Everything, including objects, is passed by value unless you use the "&" modifier. The thing about objects though is that there is a notion of "object identity": that an object is an entity distinct from every other and independent of its properties - even if another object has the same type and same property values they are still different objects (the [font=monospace]===[/font] operator tests object identity) and even if its properties change value it remains the same object.

          So when you pass an object into a function, you really are passing that object (prior to PHP5, a clone was passed). When in that function you change the object's properties, you change that object's properties.

          Variable references are another thing on top of that; [font=monospace]$a = &$b;[/font] or passing by reference makes the [font=monospace]&[/font]-prefixed variable an alias for the other. I remember seeing somewhere in bugs.php.net Rasmus comparing PHP's [font=monospace]fn(&foo)[/font] to C's [font=monospace]fn(type **foo)[/font].

            Weedpacket;11038417 wrote:

            This isn't really true. The problem here is that you've confused two different uses of the word "reference" - "reference variable" and "object reference", which is really the fault of PHP's developers for using the word "reference" in the former situation (though to be fair, the latter didn't exist in PHP until v5).

            Alright.
            (Similar to how they use the term "overloading" to mean something that no other programming language expects it too.)

            Weedpacket;11038417 wrote:

            Everything, including objects, is passed by value unless you use the "&" modifier. The thing about objects though is that there is a notion of "object identity": that an object is an entity distinct from every other and independent of its properties - even if another object has the same type and same property values they are still different objects (the [font=monospace]===[/font] operator tests object identity) and even if its properties change value it remains the same object.

            So, is there a practical difference? or is this more of an "implementation detail"?

            (This is a sincere question—what I'm getting at, for example: is there a use case for [font=monospace]&$object[/font]? does that exhibit different behavior than simply using [font=monospace]$object[/font]?)

              Also, because I just did this, references are super nice for modifying a variable instead of doing $var = func($var); you just do func($var); just like using sort/ksort etc.

                The problem here is that there are two different concepts involving the same word "reference".

                If you're familiar with C++ and Java, one way to look at this is to see that the concept of a reference variable is similiar to C++ references. Such a reference is effectively an alias that cannot be rebound as alias of different object. Thus, if you have a reference parameter (i.e., you declare it with the ampersand syntax), and you assign to the reference parameter within the function, the variable in the caller will be changed, since the reference parameter is effectively an alias for that variable (or rather the object represented by that variable).

                Objects in PHP4 were like objects in C++: passing an object by value meant that a copy was made. Thus, if you assigned to the parameter, the object in the caller remained unchanged, because it is a different object. If you modified the object say by calling a setter function, the object in the caller still remains unchanged, because it is a different object.

                Objects in PHP5, on the other hand, are similiar to Java objects: the variables are "object references", such that object assignment (copying) actually assigns the reference to the object rather than the object itself. Thus, if you assigned to the parameter, the object in the caller remains unchanged, because you're just getting the object reference to point to a different object, but the object reference in the caller continues to point to the original object. But if you modified the object say by calling a setter function, then the object in the caller would be changed, because you modified that object through a different object reference to the same object (i.e., the same "object identity" that Weedpacket mentioned in post #5). This is akin to passing (by value) a pointer to an object in C++ (or C), except that there is no pointer arithmetic and there is no need to explicitly dereference the pointer. Similiarly, there is no need to worry about the cost of copying an object (unless you clone it) since object references, like pointers, should be cheap to copy.

                So, this should answer traq's question in post #6: there is a use case for passing objects by reference in PHP5, i.e., when you do not merely want to modify the existing object, but to assign an entirely different object, e.g., for use as an out or in/out parameter as what Derokorian mentioned in post #7.

                  traq wrote:

                  (This is a sincere question—what I'm getting at, for example: is there a use case for &$object? does that exhibit different behavior than simply using $object?)

                  Yes, there is a user-visible difference. Whether it's useful or not is left to the reader.

                  class Foo
                  {
                    public $prop = 42;
                  }
                  
                  function without_reference($object)
                  {
                      $object->prop = 99;
                      $object = new Foo;
                      $object->prop = 17;
                  }
                  
                  function with_reference(&$object)
                  {
                      $object->prop = 99;
                      $object = new Foo;
                      $object->prop = 17;
                  }
                  
                  $o1 = new Foo;
                  $o2 = new Foo;
                  echo spl_object_hash($o1), "\t", $o1->prop, "\n";
                  echo spl_object_hash($o2), "\t", $o2->prop, "\n";
                  echo "\n";
                  without_reference($o1);
                  echo spl_object_hash($o1), "\t", $o1->prop, "\n";
                  with_reference($o2);
                  echo spl_object_hash($o2), "\t", $o2->prop, "\n";
                  

                    Thanks laserlight, Weedpacket

                      Yes, thanks all! I have been too busy to check back on this topic but hope to digest its lessons this weekend.

                        Write a Reply...