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'
なぜこれがなぜなのかを説明できますか?参照が何らかの形で静的継承にどのように影響するかがわかりません:/
アップデート:
に基づく Artefactoの答え, 、ベースクラス(この場合、a)に次の方法を使用し、クラス宣言後に呼び出すと、セッターに参照によって割り当てる必要なく、上記の「望ましい」とラベル付けされた動作を生成し、結果を自己を使用するときに残します。 :上記の「予想される」動作として。
/*...*/
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は書き込みにコピーを使用します。つまり、同じコンテンツがある場合、値の同じ実際のメモリ表現(zVal)を共有しようとします。 inherit_static_prop
サブクラスにコピーできるように、スーパークラスの静的プロパティのそれぞれに対して呼び出されます。の実装 inherit_static_prop
サブクラスの静的特性がPHP参照になることを保証します。親のZvalが共有されるかどうか(特に、スーパークラスに参照がある場合、子供はZVALを共有します。コピーされると、New Zvalが参照されます。2番目のケースはここでは本当に興味がありません)。
したがって、基本的に、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;
3つの変数すべてが同じZVALとその参照を共有するため、3つの変数すべてが値を想定します $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の現在削除された回答で紹介されているように、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
).