PHP(5.3)的特殊行为,静态继承和引用

con*_*nec 10 php inheritance reference 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'

?>
Run Code Online (Sandbox Code Playgroud)

现在,就我而言,这是静态继承的非常理想的行为,然而,static::$a =& $v;改为static::$a = $v;(无引用)你得到我期望的行为,即:

'A'
'A'
'A'

'B'
'B'
'B'

'C'
'C'
'C'
Run Code Online (Sandbox Code Playgroud)

谁能解释为什么会这样?我无法理解引用如何以任何方式影响静态继承:/

更新:

基于Artefacto的答案,在基类中使用以下方法(在本例中为A)并在类声明之后调用它会产生上面标记为"desired"的行为,而不需要在setter中通过引用分配,同时保留结果当使用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();
/*...*/
Run Code Online (Sandbox Code Playgroud)

Art*_*cto 11

TL; DR版本

static属性$a在每个类中都是一个不同的符号,但实际上它是in 中的相同变量$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);
Run Code Online (Sandbox Code Playgroud)

其定义是:

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;
}
Run Code Online (Sandbox Code Playgroud)

让我们翻译一下.PHP使用copy on write,这意味着如果它们具有相同的内容,它将尝试共享相同的实际内存表示(zval).inherit_static_prop为每个超类静态属性调用,以便可以复制到子类.实现inherit_static_prop确保子类的静态属性将是PHP引用,无论父项的zval是否共享(特别是,如果超类具有引用,则子项将共享zval,如果不共享,则zval将被复制,新的zval将被作为参考;第二种情况在这里并不引起我们的兴趣).

所以基本上,当A,B和C形成时,$a对于每个类都将是一个不同的符号(即,每个类都有其属性哈希表,每个哈希表都有自己的条目$a),但是底层的zval将是相同的AND它将是一个参考.

你有类似的东西:

A::$a -> zval_1 (ref, reference count 3);
B::$a -> zval_1 (ref, reference count 3);
C::$a -> zval_1 (ref, reference count 3);
Run Code Online (Sandbox Code Playgroud)

因此,当您进行正常分配时

static::$a = $v;
Run Code Online (Sandbox Code Playgroud)

由于所有三个变量共享相同的zval及其引用,因此所有三个变量都将采用该值$v.如果你这样做会是一样的:

$a = 1;
$b = &$a;
$a = 2; //both $a and $b are now 1
Run Code Online (Sandbox Code Playgroud)

另一方面,当你这样做

static::$a =& $v;
Run Code Online (Sandbox Code Playgroud)

你将打破参考集.假设你在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);
Run Code Online (Sandbox Code Playgroud)

类似的是

$a = 1;
$b = &$a;
$v = 3;
$b = &$v; //$a is 1, $b is 3
Run Code Online (Sandbox Code Playgroud)

变通

正如Gordon现在删除的答案中所述,三个类的属性之间的引用集也可以通过重新声明每个类中的属性来打破:

class B extends A { protected static $a; }
class C extends A { protected static $a; }
Run Code Online (Sandbox Code Playgroud)

这是因为属性不会从超类,如果它重新声明复制到子类(见条件if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h))inherit_static_prop).