I\'m writing a library in PHP 5.3, the bulk of which is a class with several static properties that is extended from by subclasses to allow zero-conf for child classes.
The static property $a
is a different symbol in each one of the classes, but it's actually the same variable in the sense that in $a = 1; $b = &$a;
, $a
and $b
are the same variable (i.e., they're on the same reference set). When making a simple assignment ($b = $v;
), the value of both symbols will change; when making an assignment by reference ($b = &$v;
), only $b
will be affected.
First thing, let's understand how static properties are 'inherited'. zend_do_inheritance
iterates the superclass static properties calling 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);
The definition of which is:
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;
}
Let's translate this. PHP uses copy on write, which means it will try to share the same actual memory representation (zval) of the values if they have the same content. inherit_static_prop
is called for each one of the superclass static properties so that can be copied to the subclass. The implementation of inherit_static_prop
ensures that the static properties of the subclass will be PHP references, whether or not the zval of the parent is shared (in particular, if the superclass has a reference, the child will share the zval, if it doesn't, the zval will be copied and new zval will be made a reference; the second case doesn't really interest us here).
So basically, when A, B and C are formed, $a
will be a different symbol for each of those classes (i.e., each class has its properties hash table and each hash table has its own entry for $a
), BUT the underlying zval will be the same AND it will be a reference.
You have something like:
A::$a -> zval_1 (ref, reference count 3);
B::$a -> zval_1 (ref, reference count 3);
C::$a -> zval_1 (ref, reference count 3);
Therefore, when you do a normal assignment
static::$a = $v;
since all three variables share the same zval and its a reference, all three variables will assume the value $v
. It would be the same if you did:
$a = 1;
$b = &$a;
$a = 2; //both $a and $b are now 1
On the other hand, when you do
static::$a =& $v;
you will be breaking the reference set. Let's say you do it in class A. You now have:
//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);
The analogous would be
$a = 1;
$b = &$a;
$v = 3;
$b = &$v; //$a is 1, $b is 3
As featured in Gordon's now deleted answer, the reference set between the properties of the three classes can also be broken by redeclaring the property in each one of the classes:
class B extends A { protected static $a; }
class C extends A { protected static $a; }
This is because the property will not be copied to the subclass from the superclass if it's redeclared (see the condition if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h))
in inherit_static_prop
).