【问题标题】:Peculiar Behaviour with PHP (5.3), static inheritance and referencesPHP (5.3)、静态继承和引用的特殊行为
【发布时间】:2011-03-12 08:32:20
【问题描述】:

我正在用 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 =&amp; $v; 更改为 static::$a = $v;(无参考)你会得到我期望的行为,即:

'A'
'A'
'A'

'B'
'B'
'B'

'C'
'C'
'C'

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

更新:

基于Artefacto's answer,在基类(在本例中为A)中具有以下方法并在类声明后调用它会产生上面标记为“期望”的行为,而无需在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();
/*...*/

【问题讨论】:

  • 这很有趣;我不知道。除非有人想出答案,否则你会让我浪费时间调查它:p
  • 可能与 5.3 的新后期静态绑定有关
  • 另外,使用 get_call_class() 而不是 $v 不起作用,因为它不能通过引用分配。但是,使用中间变量作为参考的工作方式如上。
  • @John Conde:问题是后期静态绑定语法 (static::$a) 用于这两种情况,但考虑到后期静态绑定的目标,我会考虑预期的结果。
  • 出于好奇,B 和 C 是 A 的实际特化,还是 A 是所有类都将从中继承的某种基本上帝对象,无论它们是否相关。

标签: php inheritance reference php-5.3


【解决方案1】:

TL;DR 版本

静态属性$a 在每个类中是不同的符号,但实际上它是同一个变量,因为$a = 1; $b = &amp;$a;$a$b 是同一个变量(即,它们'在同一个参考集上)。进行简单赋值时($b = $v;),两个符号的值都会发生变化;通过引用 ($b = &amp;$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;

您将破坏参考集。假设你在 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

解决方法

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

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

这是因为如果重新声明该属性将不会从超类复制到子类(参见inherit_static_prop 中的条件if (!zend_hash_quick_exists(target, key-&gt;arKey, key-&gt;nKeyLength, key-&gt;h)))。

【讨论】:

  • 哇,感谢您的准确解释 :) 很高兴知道它是如何工作的!
  • @Minty 看起来并不是唯一一个认为这是奇怪行为的人:bugs.php.net/bug.php?id=51720
  • 它没有被删除。它是隐藏的,只有拥有 10k 声誉的俱乐部会员才能访问:D
  • Artefacto,感谢您的精彩解释。来自 C#,这种行为真的让我陷入了循环。
猜你喜欢
  • 2012-01-24
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 2011-11-10
  • 2011-05-15
  • 2011-12-28
  • 2016-02-18
  • 2010-10-06
相关资源
最近更新 更多