具有PHP(5.3)的特殊行为,静态继承和参考
-
02-10-2019 - |
题
我正在撰写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)并在类声明产生上面标记为“所需”的行为之后调用它,而无需通过setter中的参考分配,同时在使用自我时离开结果: :作为上面的“预期”行为。
/*...*/
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
迭代超类静态属性调用 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使用Write上的副本,这意味着如果它们具有相同的内容,它将尝试共享值相同的实际内存表示(Zval)。 inherit_static_prop
为每个超类静态属性都称为,因此可以复制到子类。实施 inherit_static_prop
确保子类的静态属性将是PHP引用,无论是共享父的Zval是否共享(特别是,如果超级类有参考,孩子将共享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
工作
正如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
).