重复的数组键(注意:多次从__sleep()返回的成员变量“ a”)

标题似乎有点傻,但是我对此非常认真。今天在工作中,我遇到了无法解释的怪异的PHP行为。幸运的是,此行为已在PHP 7.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的类A和B。细心的读者注意到,属性$a具有两种不同的可见性(公共,私有)。到目前为止还算不上什么。魔术发生在__sleep方法中,当我们serialize实例时被神奇地调用。我们希望使用get_object_vars获得的所有对象变量将其简化为array_keys的键,并使用var_dump输出所有内容。

我会期望像这样(自PHP 7.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中我无法生成具有两个完全相同的键的数组?还是我想念这里明显的东西?

我的同事们最初不想让我相信,但是在了解了这里发生的一切之后,他们都没有一个很好的解释。

我真的很想看到一个很好的解释。

jiguanni 回答:重复的数组键(注意:多次从__sleep()返回的成员变量“ a”)

我找不到问题中的错误报告,但有趣的是,this commit似乎解决了同一件事:

  

如果我们在可见私有属性的范围内,则不应该看到私有公共属性。

测试代码写得很好,只需简单的更改,我们就可以在这里使用它:

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,因为这两个具有相同键名的元素不会在哈希表内部存储有相同的键。也就是说,除了潜在的冲突以外,这些都存储为唯一的整数,并且由于这种情况已在内部发生,因此忽略了对用户输入的限制。

,

经过一番弄乱后,看来这并不取决于__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());

PHP 7.0-7.3的输出

array(2) {
  ["a"]=>
  string(17) "This is $a from B"
  ["a"]=>
  string(17) "This is $a from A"
}

我认为父母中的私人$a与孩子中的公共$a是不同的财产。当您更改B中的可见性时,您并没有更改$aA中的可见性,实际上是在创建一个具有相同名称的新属性。如果您var_dump对象本身,则可以看到这两个属性。

它应该不会有太大的作用,因为您将无法从子类的父类访问私有属性,即使您可以看到它在早期的PHP 7版本中也存在。

,

我的几分钱。

我不了解同事,但我不相信并认为这是个玩笑。

作为解释-肯定是问题在“ get_object_vars”变量下,因为它返回重复的关联数组。对于同一键,应该是两个不同的哈希表值(这是不可能的,但唯一的解释是)。我找不到与内部get_object_vars()实现的任何链接(即使PHP基于开放源代码,因此也可能以某种方式获取代码和进行调试)。我也正在思考(到目前为止未成功)在内存中查看包含哈希表的数组表示的方法。另一方面,我能够使用PHP的“合法”功能并使用数组做一些技巧。

这是我尝试使用该关联数组测试某些功能的尝试。以下是输出。无需任何解释-您可以看到所有内容并亲自尝试相同的代码,因此只需注释。

  1. 我的环境是php 7.2.12 x86(32位)-是的,是的,对我感到羞耻

  2. 我摆脱了“魔术”和序列化,只剩下带来问题的东西。

  3. 完成了对类A和B以及函数调用的一些重构。

  4. A类下的$ key必须是私有的,否则没有奇迹。

  5. 零件测试变量-除主要问题外没有其他有趣的事情。

  6. 零件测试copy_vars-重复复制了数组!!新密钥已成功添加。

  7. 零件测试迭代和new_vars-迭代经历了重复而没有问题,但是新数组不接受重复,接受了最后一个键。

  8. 测试替换-在第二把钥匙上完成替换,重复入住。

  9. 测试ksort-数组未更改,重复项未被识别

  10. 测试分类-更改值并运行分类后,我能够更改顺序并交换重复的键。现在,当我们按键调用数组或分配键时,第一个键变为第二个键,而新键为该键。结果,我能够更改两个键!!在我以为重复键是一种看不见的键之前,现在很明显,当我们引用或分配键时,最后一个键有效。

  11. 转换为stdClass对象-没办法!仅接受最后一个键!

  12. 测试未设置-做得好!删除了最后一个键,但是第一个键由负责,只有一个键,没有重复。

  13. 内部表示测试-这是一个添加其他功能并查看重复项的主题。我现在正在考虑。

结果输出在代码下方。

<?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://www.f2er.com/3157041.html

大家都在问