Своеобразное поведение с PHP (5.3), статическое наследование и ссылки - PullRequest
10 голосов
/ 06 июля 2010

Я пишу библиотеку в 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; на static::$a = $v; (без ссылки), вы получите ожидаемое мной поведение, а именно:

'A'
'A'
'A'

'B'
'B'
'B'

'C'
'C'
'C'

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

Обновление:

На основании ответа Артефакто , имеющего следующий метод в базовом классе (в данном случае, 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();
/*...*/

1 Ответ

11 голосов
/ 06 июля 2010

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, если он не't, 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;

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

//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

Обход

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

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).

...