这个标题看起来有点傻,但我完全是认真的。今天在工作中,我遇到了一个奇怪的PHP行为,我无法解释。幸运的是,这种行为在PHP7.4中得到了修正,因此似乎有人也无意中发现了这一点。
我举了一个小例子来说明出了什么问题:
<?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);
在这里运行以下代码:https://3v4l.org/DBt3o
以下是对这里所发生的事情的一点解释。我们必须使用A类和B类,它们都共享一个属性$a
。仔细的读者注意到,属性$a
有两个不同的可见性(公共的,私有的)。到目前为止还没什么好想的。魔术发生在__sleep
方法中,当我们调用实例时,这个方法会被神奇地调用。我们希望拥有使用get_object_vars
获得的所有对象变量,将其简化为只使用array_keys
的键,并使用var_dump
输出所有内容。
我预计会出现这样的情况(这种情况发生在PHP7.4之后,也是我的预期输出):
array(2) {
[0]=>
string(1) "b"
[1]=>
string(1) "a"
}
但我得到的是:
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "a"
}
PHP怎么会用两个完全相同的键传递一个数组呢?谁能够解释在内部发生的事情,因为在普通PHP中,我无法生成两个完全相同的键的数组?还是我错过了一些显而易见的东西?
我的同事一开始不想相信我,但他们中没有一个人在了解了这里发生的事情后对此有一个很好的解释。
我真的很想看到一个很好的解释。
发布于 2019-11-07 16:01:41
我在问题中找不到bug的报告,但有趣的是,此承诺似乎解决了同样的问题:
如果我们在可见的范围内,便不应看到隐藏的公共财产。
测试代码编写得很好,只要做一个简单的修改,我们就可以在这里得到它:
class Test
{
private $prop = "Test";
function run()
{
return get_object_vars($this);
}
}
class Test2 extends Test
{
public $prop = "Test2";
}
$props = (new Test2)->run();
在var_dump()
上调用$props
显示:
array(2) {
["prop"]=>
string(5) "Test2"
["prop"]=>
string(4) "Test"
}
回到你的问题:
PHP怎么会用两个完全相同的键传递一个数组呢?谁能够解释在内部发生的事情,因为在普通PHP中,我无法生成两个完全相同的键的数组?
是的,您不能有一个具有两个相同键的数组:
var_dump(array_flip(array_flip($props)));
在以下方面的成果:
array(1) {
["prop"]=>
string(4) "Test"
}
但是,让我不同意您对two completely identical keys
的看法,因为这两个具有相同密钥名的元素不会在哈希表内部使用相同的密钥存储。也就是说,除了潜在的冲突之外,这些被存储为唯一的整数,并且由于这是在内部发生的,对用户输入的限制被忽略了。
发布于 2019-11-05 11:44:02
在对此进行了一些处理之后,看起来这不依赖于__sleep()
。
显然,在PHP 7的早期版本中,情况总是如此(但在PHP 5中显然并非如此)。这个较小的例子显示了相同的行为。
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());
PHP7.0- 7.3输出
array(2) {
["a"]=>
string(17) "This is $a from B"
["a"]=>
string(17) "This is $a from A"
}
我认为父类中的私有$a
与子层中的公共$a
是不同的属性。当您在B
中更改可见性时,您没有在A
中更改$a
的可见性,而是使用相同的名称创建一个新属性。如果您var_dump
对象本身,您可以看到这两个属性。
但是,它不应该有太大的效果,因为您将无法从子类中的父类访问私有属性,即使您可以看到它存在于早期的PHP 7版本中。
发布于 2019-11-08 00:49:53
我的两分钱。
我不知道同事,但我不相信,认为这是一个笑话。
为了解释这个问题,这个问题肯定是在"get_object_vars“变量下,因为它返回的是重复的关联数组。应该是相同键的两个不同的哈希表值(这是不可能的,但唯一的解释是)。我无法找到指向内部get_object_vars()实现的任何链接(尽管PHP基于开放源码,因此可以以某种方式获得代码和调试)。此外,我正在思考(到目前为止)在内存中看到数组表示形式(包括哈希表)的过程中(到目前为止没有成功)。另一方面,我能够使用PHP的“合法”函数,并对数组做一些技巧。
这是我尝试用关联数组测试一些功能的尝试。下面是输出。不需要解释-你可以看到所有的东西,自己尝试相同的代码,所以只有一些评论。
结果输出低于代码。
<?php
class A {
private $key = 'This is $a from A';
protected function funcA() {
$vars = get_object_vars($this);
return $vars;
}
}
class B extends A
{
public $key = 'This is $a from B';
public function funcB() {
return $this->funcA();
}
}
$b = new B();
$vars = $b->funcB();
echo "testing vars:\n\n\n";
var_dump($vars);
var_dump($vars['key']);
var_dump(array_keys($vars));
echo "\n\n\ntesting copy_vars:\n\n\n";
$copy_vars = $vars;
$copy_vars['new_key'] = 'this is a new key';
var_dump($vars);
var_dump($copy_vars);
echo "\n\n\ntesting iteration and new_vars:\n\n\n";
$new_vars = [];
foreach($vars as $key => $val) {
echo "adding '$key', '$val'\n";
$new_vars[$key] = $val;
}
var_dump($new_vars);
echo "\n\n\ntesting replace key (for copy):\n\n\n";
var_dump($copy_vars);
$copy_vars['key'] = 'new key';
var_dump($copy_vars);
echo "\n\n\ntesting key sort (for copy):\n\n\n";
var_dump($copy_vars);
ksort($copy_vars);
var_dump($copy_vars);
echo "\n\n\ntesting asort (for copy):\n\n\n";
$copy_vars['key'] = "A - first";
var_dump($copy_vars);
asort($copy_vars);
var_dump($copy_vars);
$copy_vars['key'] = "Z - last";
var_dump($copy_vars);
echo "\n\n\ntesting object conversion (for copy):\n\n\n";
var_dump($copy_vars);
$object = json_decode(json_encode($copy_vars), FALSE);
var_dump($object);
echo "\n\n\ntesting unset (for copy):\n\n\n";
var_dump($copy_vars);
unset($copy_vars['key']);
var_dump($copy_vars);
echo "\n\n\ntesting inernal representation:\n\n\n";
debug_zval_dump($vars);
现在的产出:
testing vars:
array(2) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(17) "This is $a from A"
}
string(17) "This is $a from A"
array(2) {
[0]=>
string(3) "key"
[1]=>
string(3) "key"
}
testing copy_vars:
array(2) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(17) "This is $a from A"
}
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(17) "This is $a from A"
["new_key"]=>
string(17) "this is a new key"
}
testing iteration and new_vars:
adding 'key', 'This is $a from B'
adding 'key', 'This is $a from A'
array(1) {
["key"]=>
string(17) "This is $a from A"
}
testing replace key (for copy):
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(17) "This is $a from A"
["new_key"]=>
string(17) "this is a new key"
}
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(7) "new key"
["new_key"]=>
string(17) "this is a new key"
}
testing key sort (for copy):
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(7) "new key"
["new_key"]=>
string(17) "this is a new key"
}
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(7) "new key"
["new_key"]=>
string(17) "this is a new key"
}
testing asort (for copy):
array(3) {
["key"]=>
string(17) "This is $a from B"
["key"]=>
string(9) "A - first"
["new_key"]=>
string(17) "this is a new key"
}
array(3) {
["key"]=>
string(9) "A - first"
["key"]=>
string(17) "This is $a from B"
["new_key"]=>
string(17) "this is a new key"
}
array(3) {
["key"]=>
string(9) "A - first"
["key"]=>
string(8) "Z - last"
["new_key"]=>
string(17) "this is a new key"
}
testing object conversion (for copy):
array(3) {
["key"]=>
string(9) "A - first"
["key"]=>
string(8) "Z - last"
["new_key"]=>
string(17) "this is a new key"
}
object(stdClass)#2 (2) {
["key"]=>
string(8) "Z - last"
["new_key"]=>
string(17) "this is a new key"
}
testing unset (for copy):
array(3) {
["key"]=>
string(9) "A - first"
["key"]=>
string(8) "Z - last"
["new_key"]=>
string(17) "this is a new key"
}
array(2) {
["key"]=>
string(9) "A - first"
["new_key"]=>
string(17) "this is a new key"
}
testing inernal representation:
array(2) refcount(2){
["key"]=>
string(17) "This is $a from B" refcount(2)
["key"]=>
string(17) "This is $a from A" refcount(4)
}
https://stackoverflow.com/questions/58717899
复制相似问题