Сложное поведение с PHP (5.3), статическое наследование и ссылки

Я пишу библиотеку в PHP 5.3, основная часть которой представляет собой класс с несколькими статическими свойствами, который расширяется из подклассов, чтобы разрешить ноль-conf для дочерних классов.

Во всяком случае, вот образец, чтобы проиллюстрировать ту особенность, которую я нашел:

<?php class A { protected static $a; public static function out() { var_dump(static::$a); } public static function setup($v) { static::$a =& $v; } } class B extends A {} class C extends A {} A::setup('A'); A::out(); // 'A' B::out(); // null C::out(); // null B::setup('B'); A::out(); // 'A' B::out(); // 'B' C::out(); // null C::setup('C'); A::out(); // 'A' B::out(); // 'B' C::out(); // 'C' ?> 

Теперь это очень желаемое поведение для статического наследования, насколько я могу судить, однако, изменяя static::$a =& $v; to static::$a = $v; (нет ссылки), вы получите ожидаемое поведение, то есть:

 'A' 'A' 'A' 'B' 'B' 'B' 'C' 'C' 'C' 

Может ли кто-нибудь объяснить, почему это так? Я не могу понять, как ссылки влияют на статическое наследование любым способом: /

Обновить:

Основываясь на ответе Artefacto , имея следующий метод в базовом классе (в данном случае A) и вызывая его после объявления класса, выдает поведение, помеченное как «желаемое» выше, без необходимости назначать по ссылке в сеттерах, оставляя результаты при использовании self :: как «ожидаемое» поведение выше.

 /*...*/ public static function break_static_references() { $self = new ReflectionClass(get_called_class()); foreach($self->getStaticProperties() as $var => $val) static::$$var =& $val; } /*...*/ A::break_static_references(); B::break_static_references(); C::break_static_references(); /*...*/ 

Версия TL; DR

Статическое свойство $a является другим символом в каждом из классов, но на самом деле это одна и та же переменная в том смысле, что в $a = 1; $b = &$a; $a = 1; $b = &$a; , $a и $b$a и та же переменная (т. е. они находятся в одном наборе ссылок). При создании простого присваивания ( $b = $v; ) значение обоих символов будет изменено; при выполнении задания по ссылке ( $b = &$v; ) будет затронуто только $b .

Оригинальная версия

Во-первых, давайте разобраться, как статические свойства «унаследованы». zend_do_inheritance статические свойства суперкласса, вызывающие inherit_static_prop :

 zend_hash_apply_with_arguments(&parent_ce->default_static_members TSRMLS_CC, (apply_func_args_t)inherit_static_prop, 1, &ce->default_static_members); 

Определение которого:

 static int inherit_static_prop(zval **p TSRMLS_DC, int num_args, va_list args, const zend_hash_key *key) { HashTable *target = va_arg(args, HashTable*); if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h)) { SEPARATE_ZVAL_TO_MAKE_IS_REF(p); if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) { Z_ADDREF_PP(p); } } return ZEND_HASH_APPLY_KEEP; } 

Давайте переведем это. PHP использует копию при записи, что означает, что он попытается использовать одно и то же представление реальной памяти (zval) значений, если они имеют одинаковый контент. inherit_static_prop вызывается для каждого из статических свойств суперкласса, поэтому его можно скопировать в подкласс. Реализация inherit_static_prop гарантирует, что статические свойства подкласса будут ссылками PHP, независимо от того, является ли разделение zval родителя (в частности, если суперкласс имеет ссылку, ребенок будет делиться zval, если он не , zval будет скопирован, и новый zval станет ссылкой, второй случай нас действительно не интересует).

Таким образом, в основном, когда формируются A, B и C, $a будет другим символом для каждого из этих классов (т. Е. Каждый класс имеет свою хэш-таблицу свойств, и каждая хэш-таблица имеет свою собственную запись для $a ), НО zval будет тем же И это будет ссылка.

У вас есть что-то вроде:

 A::$a -> zval_1 (ref, reference count 3); B::$a -> zval_1 (ref, reference count 3); C::$a -> zval_1 (ref, reference count 3); 

Поэтому, когда вы выполняете обычное задание

 static::$a = $v; 

поскольку все три переменных имеют один и тот же zval и его ссылку, все три переменные будут принимать значение $v . Было бы так же, если бы вы сделали:

 $a = 1; $b = &$a; $a = 2; //both $a and $b are now 1 

С другой стороны, когда вы это делаете

 static::$a =& $v; 

вы будете нарушать набор ссылок. Предположим, вы делаете это в классе A. Теперь у вас есть:

 //reference count is 2 and ref flag is set, but as soon as //$v goes out of scope, reference count will be 1 and //the reference flag will be cleared A::$a -> zval_2 (ref, reference count 2); B::$a -> zval_1 (ref, reference count 2); C::$a -> zval_1 (ref, reference count 2); 

Аналогичным было бы

 $a = 1; $b = &$a; $v = 3; $b = &$v; //$a is 1, $b is 3 

Работа вокруг

Как показано в теперь удаленном ответе Гордона, набор ссылок между свойствами трех классов также может быть нарушен путем повторного использования свойства в каждом из классов:

 class B extends A { protected static $a; } class C extends A { protected static $a; } 

Это связано с тем, что свойство не будет скопировано в подкласс из суперкласса, если оно обновлено (см. Условие if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h)) в inherit_static_prop ).