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.
Anyway, here’s a sample to illustrate the peculiarity I have found:
<?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'
?>
Now, this is pretty much desired behaviour for static inheritance as far as I’m concerned, however, changing static::$a =& $v; to static::$a = $v; (no reference) you get the behaviour I expected, that is:
'A'
'A'
'A'
'B'
'B'
'B'
'C'
'C'
'C'
Can anyone explain why this is? I can’t understand how references effect static inheritance in any way :/
Update:
Based on Artefacto’s answer, having the following method in the base class (in this instance, A) and calling it after the class declarations produces the behaviour labelled as ‘desired’ above without the need to assign by reference in setters, whilst leaving the results when using self:: as the ‘expected’ behaviour above.
/*...*/
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 version
The static property
$ais 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;,$aand$bare 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$bwill be affected.Original version
First thing, let’s understand how static properties are ‘inherited’.
zend_do_inheritanceiterates the superclass static properties callinginherit_static_prop:The definition of which is:
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_propis called for each one of the superclass static properties so that can be copied to the subclass. The implementation ofinherit_static_propensures 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,
$awill 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:
Therefore, when you do a normal assignment
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:On the other hand, when you do
you will be breaking the reference set. Let’s say you do it in class A. You now have:
The analogous would be
Work-around
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:
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))ininherit_static_prop).