The title may seem a bit silly but I'm totally serious with this. Today at work I came across a weird PHP behaviour which I could not explain. Luckily this behaviour is fixed in PHP 7.4, so it seems that someone stumbled upon that, too.
I made a small example to illustrate what went wrong:
<?php
class A {
private $a = 'This is $a from A';
public $b = 'This is $b from A';
public function __sleep(): array
{
var_dump(array_keys(get_object_vars($this)));
return [];
}
}
class B extends A
{
public $a = 'This is $a from B';
}
$b = new B;
serialize($b);
Run this code here: https://3v4l.org/DBt3o
Heres a bit explanation of what is going on here. We have to classes A and B which both share a property $a
. Careful readers noticed, that the property $a
has two different visibilities (public, private). Nothing fancy so far. The magic happens in the __sleep
method which gets magically called when we serialize
our instance. We want to have all object variables which we get with get_object_vars
reduce this to only the keys with array_keys
and output everything with var_dump
.
I would expect something like this (this happens since PHP 7.4 and is my expected output):
array(2) {
[0]=>
string(1) "b"
[1]=>
string(1) "a"
}
But what I get is this:
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "a"
}
How could it be, that PHP delivers an array with two completely identical keys? Who is able to explain what happens here internally because in plain PHP I'm not able to generate an array with two completely identical keys? Or do I miss something obvious here?
My coworkers did not want to believe me at first but none of them had a good explanation of this after they understood what is happening here.
I really would love to see a good explanation.
The array_unique() function removes duplicate values from an array. If two or more array values are the same, the first appearance will be kept and the other will be removed. Note: The returned array will keep the first array item's key type.
The array_intersect() function compares the values of two (or more) arrays, and returns the matches. This function compares the values of two or more arrays, and return an array that contains the entries from array1 that are present in array2, array3, etc.
I couldn't find a report for the bug in the question but interestingly it seems this commit addresses the same thing:
If we are in a scope where the shadowed private property is visible, the shadowing public property should not be visible.
The test code is well-written, with a simple change we could have it here:
class Test
{
private $prop = "Test";
function run()
{
return get_object_vars($this);
}
}
class Test2 extends Test
{
public $prop = "Test2";
}
$props = (new Test2)->run();
Calling var_dump()
on $props
shows:
array(2) {
["prop"]=>
string(5) "Test2"
["prop"]=>
string(4) "Test"
}
Back to your question:
How could it be, that PHP delivers an array with two completely identical keys? Who is able to explain what happens here internally because in plain PHP I'm not able to generate an array with two completely identical keys?
Yes, you are not able to have an array with two identical keys:
var_dump(array_flip(array_flip($props)));
results in:
array(1) {
["prop"]=>
string(4) "Test"
}
but let me not agree with you on two completely identical keys
as these two elements with identical key names aren't stored with identical keys internally within a hashtable. That is, those are stored as unique integers except on potential collisions and as this has been occurring internally the restriction on user inputs was ignored.
After messing with this a bit, it looks like this doesn't depend on __sleep()
.
Apparently this was always the case in earlier versions of PHP 7 (but apparently not in PHP 5). This smaller example shows the same behavior.
class A {
private $a = 'This is $a from A';
public function showProperties() { return get_object_vars($this); }
}
class B extends A
{
public $a = 'This is $a from B';
}
$b = new B;
var_dump($b->showProperties());
Output from PHP 7.0 - 7.3
array(2) {
["a"]=>
string(17) "This is $a from B"
["a"]=>
string(17) "This is $a from A"
}
I think the private $a
in the parent is a different property than the public $a
in the child. When you change the visibility in B
you're not changing the visibility of the $a
in A
, you're really making a new property with the same name. If you var_dump
the object itself you can see both properties.
It shouldn't have much effect though, since you wouldn't be able to access the private property from the parent class in the child class, even though you can see it exists in those earlier PHP 7 versions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With