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 $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.
Original version
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
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:
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).
Related
I'm going through some PHP question for my exam, and in the question below, apparently (B) is the answer.
What is the output of the following code?
class Magic {
public $a = 'A';
protected $b = array('a' => 'A', 'b' => 'B', 'c' => 'C');
protected $c = array(1, 2, 3);
public function __get($v) {
echo "$v,";
return $this->b[$v];
}
public function __set($var, $val) {
echo "$var: $val,";
$this->$var = $val;
}
}
$m = new Magic;
echo $m->a.','.$m->b.','.$m->c.',';
$m->c = 'CC';
echo $m->a.','.$m->b.','.$m->c;
A: A,Array,Array,A,Array,Array,CC
B: b,c,A,B,C,c: CC,b,c,A,B,C
C: a,b,c,A,B,C,c: CC,a,b,c,A,B,C
D: b,c,A,B,C,c: CC,b,c,A,B,CC
Sorry for the noob question, but coming from Java, I can't for the life of me understand why this is the correct answer.
$b and $c are a protected properties so they cannot be set from outside the class scope. $a is public so it can be set/accessed directly.
For accessing $b and $c, it will fallback to the magic getter which retrieves the values from the $b array.
The logic follows:
b, <- getter echo (executed by $m->b)
c, <- getter echo (executed by $m->c)
A, <- public property value (this is the start of the first global echo expression)
B, <- getter return b[b]
C, <- getter return b[c]
c: CC, <- setter echo, sets c = CC, but c is never accessed
b, <- getter echo (executed by $m->b)
c, <- getter echo (executed by $m->c)
A, <- public property value (this is the start of the second global echo expression)
B, <- getter return b[b]
C <- getter return b[c]
The getter echoes are processed first because their echo statements are reached before the echoed expression (with concatenation) has finished evaluating.
Since $a is public, no magic setters or getters are used.
Does this cut down version make it any clearer?
class Magic
{
public $a = "A";
protected $b = ['a' => 'A', 'b' => 'B', 'c' => 'C'];
public function __get($v)
{
echo "A MAGIC METHOD IS BEING CALLED TO GET THE PROPERTY $v", PHP_EOL;
return $this->b[$v];
}
}
$m = new Magic;
echo ($m->a . ',' . $m->b . ',' . $m->c);
A MAGIC METHOD IS BEING CALLED TO GET THE PROPERTY b
A MAGIC METHOD IS BEING CALLED TO GET THE PROPERTY c
A,B,C
I suppose the point is to demonstrate two things:
That PHP's magic __get method is only called for properties that are not directly accessible (i.e. not public). The implementation of the method can then return any string at all - in this case the property name is used to look up an element of a different array.
That variables concatenated into a string are resolved before the string is used. So the __get method is called twice (for the inaccessible properties b and c), and the echo statement inside that method is called before the string itself is concatenated and displayed.
The call to set the property c between the two "echo" lines is similarly resolved using the __set method, although it doesn't haven't any impact on the rest of the code.
Demo: https://3v4l.org/4Bitg
The apparent answer is wrong (Or you had a typo while writing it down):
B: b,c,A,B,C,c: CC,b,c,A,B,C
The sequence C,c is not possible, it has to be c,C, when accessing $m->c.
SO,
The problem
It's not well-known, but PHP allows to compare objects - and not just on equality == - but on < and > too. But - how it works? So if I want to create comparable objects - what restrictions/rules they should follow?
Most useful case is with DateTime() objects - they hold certain timestamp and they could be compared (and this has logical sense). On lxr there's some explanation for DateTime . But what about common case?
I have:
class C
{
protected $holder;
protected $mirror;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//false, false, true: used $holder
var_dump($one>$two, $one==$two, $one<$two);
-if I'll change properties declaration order, it will use $mirror:
class C
{
//only order changed:
protected $mirror;
protected $holder;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//true, false, false: used $mirror
var_dump($one>$two, $one==$two, $one<$two);
So it seems one of the 'rules' is that it will use first declared property. But why is it using protected property at all is not clear to me too.
Now, more complex sample:
class Test
{
protected $a;
protected $b;
function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
$x = new Test(1, 2);
$y = new Test(1, 3);
// true, false, false
var_dump($x < $y, $x == $y, $x > $y);
$x = new Test(3, 1);
$y = new Test(2, 1);
// false, false, true
var_dump($x < $y, $x == $y, $x > $y);
-so it will use first not-equal property for comparison. But code snippets above are only some cases. I want to know exactly how it's happening and why. Thus,
Question
Is: how it works? I mean, more detailed:
Can I rely on fact, that PHP will use first not-equal property for comparison?
What will be done if count of properties isn't equal? (i.e. some property was dynamically added to instance during code execution)
Can I treat protected/private properties as to be counted for such comparison always?
e.t.c. - so if there are some additional conditions/restrictions/rules that will affect result - please, post. Documentation states only for ==/=== comparison. Also, comparison of instances of different classes is out of the issue since it'll return false (obviously).
PHP compares sequentially (in the order of declaration) the object properties and stops at the first inequal property found. This behavior is not documented, so there's not much to be said about it, sadly, other than looking at the source of PHP.
Not documented is usually a synonym of "don't rely on it".
Each class in php has an associated structure (in the c code) of handler functions, it looks like
struct _zend_object_handlers {
/* general object functions */
zend_object_add_ref_t add_ref;
zend_object_del_ref_t del_ref;
[...]
zend_object_compare_t compare_objects;
[...]
};
compare_objects points to a function that "takes two objects" and returns -1,0,1 according to whatever this comparator defines as the order (just like strcmp() does for strings).
This function is used only when both operands (objects) point to the same comparision function - but let's just stick with this case.
That's where e.g. DateTime "adds" its feature to compare two DateTime instances, it just defines another, DateTime-specific compare_objects function and puts it in the structure describing its class.
static void date_register_classes(TSRMLS_D)
{
[...]
INIT_CLASS_ENTRY(ce_date, "DateTime", date_funcs_date);
ce_date.create_object = date_object_new_date;
[...]
date_object_handlers_date.compare_objects = date_object_compare_date;
So if you want to know (exactly) how two DateTime instances are compared, take a look at date_object_compare_date.
The comparision described in the manual (at least for the case cmp(o1,o2)==0) seems to be implemented in zend_std_compare_objects. And it's used by both StdClass and a simple user defined class like e.g.
<?php
class Foo { }
$a = new StdClass;
$b = new Foo;
$a > $b;
But other classes (in php extensions) do set other functions. DateTime, ArrayObject, PDOStatement, even Closures use different functions.
But I haven't found a way to define a comparision function/method in script code (but haven't looked too hard/long)
The exact behavior is defined in the PHP language specification, thus, you can rely on it.
[…] if the objects are of different types, the comparison result is FALSE. If the objects are of the same type, the properties of the objects are compares [sic] using the array comparison described above.
And the array comparison is defined as follows:
[…] For arrays having the same numbers of elements, the keys from the left operand are considered one by one, if the next key in the left-hand operand exists in the right-hand operand, the corresponding values are compared. If they are unequal, the array containing the lesser value is considered less-than the other one, and the comparison ends; otherwise, the process is repeated with the next element. […] If all the values are equal, then the arrays are considered equal.
Simply exchange every mention of array with object and key with property in your mind and you have the exact description of how this works. I omitted useless array specifics in above quote.
<?php
class ser {
public $a;
}
$x = new ser;
$x->b = 10;
var_dump($x);
Something like this.
Class ser has only $a property, but we can set $b to new object of this class and it works despite this class doesn't have any $b property
output
E:\XAMPP\htdocs\fun\test2.php:12:
object(kurde)[1]
public 'a' => null
public 'b' => int 10
Why this works?
Why we can add property and set it to this class while it doesn't belong exactly to this class?
How is that possible and why is that possible?
Any purpose? Sense of making this possible?
This is what PHP refers to as "overloading". This is different to overloading in almost any other object oriented language.
If you do not like it, you can use the __set magic method to throw an exception if a non-existent property is set:
public function __set($name, $value) {
throw new \Exception('Property "'.$name.'" does not exist')
}
You can tell from the comments on the documentation what the general consensus of this "feature" is.
SO,
The problem
It's not well-known, but PHP allows to compare objects - and not just on equality == - but on < and > too. But - how it works? So if I want to create comparable objects - what restrictions/rules they should follow?
Most useful case is with DateTime() objects - they hold certain timestamp and they could be compared (and this has logical sense). On lxr there's some explanation for DateTime . But what about common case?
I have:
class C
{
protected $holder;
protected $mirror;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//false, false, true: used $holder
var_dump($one>$two, $one==$two, $one<$two);
-if I'll change properties declaration order, it will use $mirror:
class C
{
//only order changed:
protected $mirror;
protected $holder;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//true, false, false: used $mirror
var_dump($one>$two, $one==$two, $one<$two);
So it seems one of the 'rules' is that it will use first declared property. But why is it using protected property at all is not clear to me too.
Now, more complex sample:
class Test
{
protected $a;
protected $b;
function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
$x = new Test(1, 2);
$y = new Test(1, 3);
// true, false, false
var_dump($x < $y, $x == $y, $x > $y);
$x = new Test(3, 1);
$y = new Test(2, 1);
// false, false, true
var_dump($x < $y, $x == $y, $x > $y);
-so it will use first not-equal property for comparison. But code snippets above are only some cases. I want to know exactly how it's happening and why. Thus,
Question
Is: how it works? I mean, more detailed:
Can I rely on fact, that PHP will use first not-equal property for comparison?
What will be done if count of properties isn't equal? (i.e. some property was dynamically added to instance during code execution)
Can I treat protected/private properties as to be counted for such comparison always?
e.t.c. - so if there are some additional conditions/restrictions/rules that will affect result - please, post. Documentation states only for ==/=== comparison. Also, comparison of instances of different classes is out of the issue since it'll return false (obviously).
PHP compares sequentially (in the order of declaration) the object properties and stops at the first inequal property found. This behavior is not documented, so there's not much to be said about it, sadly, other than looking at the source of PHP.
Not documented is usually a synonym of "don't rely on it".
Each class in php has an associated structure (in the c code) of handler functions, it looks like
struct _zend_object_handlers {
/* general object functions */
zend_object_add_ref_t add_ref;
zend_object_del_ref_t del_ref;
[...]
zend_object_compare_t compare_objects;
[...]
};
compare_objects points to a function that "takes two objects" and returns -1,0,1 according to whatever this comparator defines as the order (just like strcmp() does for strings).
This function is used only when both operands (objects) point to the same comparision function - but let's just stick with this case.
That's where e.g. DateTime "adds" its feature to compare two DateTime instances, it just defines another, DateTime-specific compare_objects function and puts it in the structure describing its class.
static void date_register_classes(TSRMLS_D)
{
[...]
INIT_CLASS_ENTRY(ce_date, "DateTime", date_funcs_date);
ce_date.create_object = date_object_new_date;
[...]
date_object_handlers_date.compare_objects = date_object_compare_date;
So if you want to know (exactly) how two DateTime instances are compared, take a look at date_object_compare_date.
The comparision described in the manual (at least for the case cmp(o1,o2)==0) seems to be implemented in zend_std_compare_objects. And it's used by both StdClass and a simple user defined class like e.g.
<?php
class Foo { }
$a = new StdClass;
$b = new Foo;
$a > $b;
But other classes (in php extensions) do set other functions. DateTime, ArrayObject, PDOStatement, even Closures use different functions.
But I haven't found a way to define a comparision function/method in script code (but haven't looked too hard/long)
The exact behavior is defined in the PHP language specification, thus, you can rely on it.
[…] if the objects are of different types, the comparison result is FALSE. If the objects are of the same type, the properties of the objects are compares [sic] using the array comparison described above.
And the array comparison is defined as follows:
[…] For arrays having the same numbers of elements, the keys from the left operand are considered one by one, if the next key in the left-hand operand exists in the right-hand operand, the corresponding values are compared. If they are unequal, the array containing the lesser value is considered less-than the other one, and the comparison ends; otherwise, the process is repeated with the next element. […] If all the values are equal, then the arrays are considered equal.
Simply exchange every mention of array with object and key with property in your mind and you have the exact description of how this works. I omitted useless array specifics in above quote.
I've come across some unexpected behavior with static variables defined inside object methods being shared across instances. This is probably known behavior, but as I browse the PHP documentation I can't find instances of statically-defined variables within object methods.
Here is a reduction of the behavior I've come across:
<?php
class Foo {
public function dofoo() {
static $i = 0;
echo $i++ . '<br>';
}
}
$f = new Foo;
$g = new Foo;
$f->dofoo(); // expected 0, got 0
$f->dofoo(); // expected 1, got 1
$f->dofoo(); // expected 2, got 2
$g->dofoo(); // expected 0, got 3
$g->dofoo(); // expected 1, got 4
$g->dofoo(); // expected 2, got 5
Now, I would have expected $i to be static per instance, but in reality $i is shared between the instances. For my own edification, could someone elaborate on why this is the case, and where it's documented on php.net?
This is the very definition of static.
If you want members to be specific to an instance of an object, then you use class properties
e.g.
<?php
class Foo
{
protected $_count = 0;
public function doFoo()
{
echo $this->_count++, '<br>';
}
}
Edit: Ok, I linked the documentation to the OOP static properties. The concept is the same though. If you read the variable scope docs you'll see:
Note: Static declarations are resolved in compile-time.
Thus when your script is compiled (before it executes) the static is "setup" (not sure what term to use). No matter how many objects you instantiate, when that function is "built" the static variable references the same copy as everyone else.
I agree that the current PHP documentation is not sufficiently clear on exactly what "scope" means for a static variable inside a non-static method.
It is of course true (as hobodave indicates) that "static" generally means "per class", but static class properties are not exactly the same thing as static variables within a (non static) method, in that the latter are "scoped" by method (every method in a class can have its own static $foo variable, but there can be at most one static class member named $foo).
And I would argue that although the PHP 5 behavior is consistent ("static" always means "one shared instance per class"), it is not the only way that PHP could behave.
For example, most people use static function variables to persist state across function calls, and for global functions the PHP behavior is exactly what most everyone would expect. So it is certainly possible to imagine a PHP interpreter that maintains the state of certain method variables across method invocation and does so "per instance", and that's actually what I also expected to happen the first time I declared a local method variable to be static.
That is what static is, it's the same variable across all instances of the class.
You want to write this so that the variable is a private member of the instance of the class.
class Foo {
private $i = 0;
public function dofoo() {
echo $this->i++ . '<br>';
}
}
The static keyword can be used with variables, or used with class methods and properties. Static variables were introduced in PHP 4 (I think, it might have been earlier). Static class members/methods were introduced in PHP 5.
So, per the manual, a static variable
Another important feature of variable scoping is the static
variable. A static variable exists only in a local function
scope, but it does not lose its value when program execution
leaves this scope.
This is consistant with the behavior you described. If you want a per instance variable, used a regular class member.
Ups 7 years it a long time but anyway here it goes.
All classes have a default constructor why am I saying this?!?
Because if you define a default behaviour in constructor each instance of the class will be affected.
Example:
namespace Statics;
class Foo
{
protected static $_count;
public function Bar()
{
return self::$_count++;
}
public function __construct()
{
self::$_count = 0;
}
}
Resulting in:
require 'Foo.php';
use Statics\Foo;
$bar = new Foo();
echo $bar->bar().'<br>';
echo $bar->bar().'<br>';
echo $bar->bar().'<br>';
$barcode = new Foo();
echo $barcode->bar().'<br>';
echo $barcode->bar().'<br>';
echo $barcode->bar().'<br>';
0
1
2
0
1
2
Every new instance from the upper class will start from 0!
The static count behaviour will NOT be shared across the multiple instances as it will be starting from the value assigned in constructor.
If you need to share data across multiple instances all you need to do is to define a static variable and assign default data outside the constructor!
Example:
namespace Statics;
class Foo
{
//default value
protected static $_count = 0;
public function Bar()
{
return self::$_count++;
}
public function __construct()
{
//do something else
}
}
Resulting in:
require 'Foo.php';
use Statics\Foo;
$bar = new Foo();
echo $bar->bar().'<br>';
echo $bar->bar().'<br>';
echo $bar->bar().'<br>';
$barcode = new Foo();
echo $barcode->bar().'<br>';
echo $barcode->bar().'<br>';
echo $barcode->bar().'<br>';
0
1
2
3
4
5
As you can see the results are completely different, the memory space allocation is the same in between class instances but it can produce different results based on how you define default value.
I hope it helped, not that the above answers are wrong but I felt that it was important to understand the all concept from this angle.
Regards, from Portugal!