четверг, февраля 10, 2011

Как на самом деле работает присваивание и передача по ссылке объектов в php5

Как-то давно, когда только появился php5, я думал, что разобрался с ООП, а именно с присвоением объектов. Но из-за того, что много времени проводил с java стал под забывать как это работает в php5.
В штатном режиме, если всегда помнить о том, что в php5 присваивание происходит по ссылке, то нет никаких проблем, пока не используешь знак &.
http://www.php.net/manual/en/language.oop5.basic.php
<?php
class SimpleClass{}
class SimpleClass
{
    // property declaration
    public $var = 'a default value';

    // method declaration
    public function displayVar() {
        echo $this->var;
    }
}
?>

Но подлинный смысл раскрывается этим комментарием:
http://www.php.net/manual/en/language.oop5.basic.php#79856
Я позволил себе его вольно перевести:
В php необходимо думать о переменных как о ячейках памяти. У каждой переменной если имя, которое ссылается на ячейку памяти(переменную), где хранится значение простого типа: число, строка, массив, и т.д. Когда вы создаете ссылку(&), вы создаете второе имя, которое ссылается на ту же саму ячейку памяти. Когда вы присваиваете одну переменную другой, вы копируете содержимое ячейки памяти в другую ячейку памяти.
Но присваивание экземпляров классов(далее объект) происходит не так как присваивание простых типов. Объекты не хранятся в ячейках памяти, которые программист "видит" на прямую. Вместо этого в ячейке памяти хранится указатель на объект. Таким образом указатель ведет себя как примитивный тип.
Когда вы присваиваете значение ссылки объекта одной переменной другой, обе переменные могут менять состояние одного и того же объекта. Но переменные не являются ссылками на объект, т.к. если присвоить одной из переменных новое значение, то это не отразится на другой переменной.
<?php
// Assignment of an object
Class Object{
   public $foo="bar";
};

$objectVar = new Object();
$reference =& $objectVar;
$assignment = $objectVar

//
// $objectVar --->+-----------+
//                |(указатель1)----+
// $reference --->+-----------+    |
//                                 |
//                +-----------+    |
// $assignment -->|(указатель2)----+
//                +-----------+    |
//                                 |
//                                 v
//                  Object(1):foo="bar"
//
?>
Значение переменной $assignment отличается от $objectVar, но эти значение ссылаются на один и тот же объект. Это поведение делает похожим на механизм передачи по ссылке. Если вы используете переменную $objectVar, чтобы поменять состояние объекта, то эти же изменения появятся и в переменной $assignment, т.к. они указывают на один и тот же объект.


<?php
$objectVar->foo = "qux";
print_r( $objectVar );
print_r( $reference );
print_r( $assignment );

//
// $objectVar --->+-----------+
//                |(указатель1)----+
// $reference --->+-----------+    |
//                                 |
//                +-----------+    |
// $assignment -->|(указатель2)----+
//                +-----------+    |
//                                 |
//                                 v
//                  Object(1):foo="qux"
//
?>

Но с передачей значения по ссылке (&) дело обстоит совсем иначе. Если вы обнулите $objectVar, вы замените значение указателя в ячейке памяти на NULL. Это означает, что $reference, которая ссылается на туже ячейку памяти, также будет NULL. Но $assignment, которая ссылается на другую ячейку памяти, будет так же хранить копию указателя на объект, и она не будет равна NULL.

<?php
$objectVar = null;
print_r($objectVar);
print_r($reference);
print_r($assignment);

//
// $objectVar --->+-----------+
//                |  NULL     | 
// $reference --->+-----------+
//                           
//                +-----------+
// $assignment -->|(указатель2)----+
//                +-----------+    |
//                                 |
//                                 v
//                  Object(1):foo="qux"
?>


К примеру, если взять java, где все переменные если ссылки на объекты, тот же пример выполняется так как и в php5 без символа &:

import java.util.*;

class ObjectReference {
    public static void main(String[] args) {
        class Obj {
            int var = 1;
        }
        
        Obj objectVar = new Obj();
        Obj reference = objectVar;
        objectVar.var = 2;
        System.out.println("objectVar.var="+objectVar.var);
        System.out.println("reference.var="+reference.var);
        
        objectVar = null;
        
        if (reference == null) {
            System.out.println("reference is null");
        } else {
            System.out.println("reference is not null");
        }
    }
}
ответ:
objectVar.var=2
reference.var=2
reference is not null