Своеобразное поведение с PHP (5.3), статическое наследование и ссылки
-
02-10-2019 - |
Вопрос
Я пишу библиотеку в Php 5.3, основная часть которой представляет собой класс с несколькими статическими свойствами, которые расширяются по подклассам, чтобы позволить нулевой конфликт для детей.
В любом случае, вот образец, чтобы проиллюстрировать особенность, которую я нашел:
<?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;
к static::$a = $v;
(нет ссылки) Вы получаете поведение, которое я ожидал, то есть:
'A'
'A'
'A'
'B'
'B'
'B'
'C'
'C'
'C'
Кто -нибудь может объяснить, почему это? Я не могу понять, как ссылки влияют на статическое наследование каким -либо образом:/
Обновлять:
На основе Ответ артефакто, имея следующий метод в базовом классе (в данном случае, а) и вызов его после того, как объявления класса создают поведение, помеченное как «желаемое» выше, без необходимости назначать ссылку в сети, в то же время оставляя результаты при использовании себя: : как «ожидаемое» поведение выше.
/*...*/
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
и $b
такая же переменная (т.е. они находятся на одном эталонном наборе). При выполнении простого задания ($b = $v;
), значение обоих символов изменится; При назначении по ссылке ($b = &$v;
), Только $b
будет затронут.
Оригинальная версия
Прежде всего, давайте поймем, как статические свойства «унаследованы». zend_do_inheritance
итерация статических свойств Superclass 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; }
Это связано с тем, что свойство не будет скопировано на подкласс из SuperClass, если оно будет переоборудовано (см. Условие if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h))
в inherit_static_prop
).