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

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

Так или иначе вот образец для иллюстрирования особенности, которую я нашел:


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

'A'
'A'
'A'

'B'
'B'
'B'

'C'
'C'
'C'

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

Обновление:

На основе ответа Artefacto, имея следующий метод в базовом классе (в этом экземпляре, A) и называя его после объявлений класса производит поведение, маркированное, как 'желаемый' выше без потребности присвоиться ссылкой в методах set, при отъезде результатов при использовании сам:: как 'ожидаемое' поведение выше.

/*...*/
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();
/*...*/

10
задан Community 23 May 2017 в 11:45
поделиться

1 ответ

TL;DR версия

Статическое свойство $a имеет разный символ в каждом из классов, но на самом деле это одна и та же переменная в том смысле, что в $a = 1; $b = &$a;, $a и $b - одна и та же переменная (т.е. они находятся на одном ссылочном множестве). При выполнении простого присваивания ($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).

11
ответ дан 4 December 2019 в 00:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: