【问题标题】:Pre-increment in PHP with magic get and set definedPHP 中的预增量,定义了魔术 get 和 set
【发布时间】:2012-05-18 04:48:35
【问题描述】:

我有一个问题一直在破坏我想做的事情的方式很长一段时间。它与在 PHP 中使用魔法 get 和 set 以及尝试对对象进行预增量有关。我有一个 PHP 类,如下所示:

class Foo {
    public $object;

    function __construct() {
            $this->object = array("bar" => 1);
    }

    function & __get($name) {
            return $this->object[$name];
    }

    function __set($name, $value) {
            echo "Old value: ". $this->object[$name] ." - New value: ". $value ."\n";
            $this->object[$name] = $value;
    }
}

注意__get 方法中的&。现在我运行这段代码:

$o = new Foo();

echo "First test\n";
$o->bar = 2;

echo "Second test\n";
$o->bar = $o->bar + 1;

echo "Third test\n";
$o->bar += 1;

echo "Fourth test\n";
++$o->bar;

输出是:

First test
Old value: 1 - New value: 2
Second test
Old value: 2 - New value: 3
Third test
Old value: 4 - New value: 4
Fourth test
Old value: 5 - New value: 5

第三个和第四个测试有一个意想不到的(我)的行为。看起来 $this->object['bar'] 返回要设置的值,而不是我预期的旧值。为什么值在实际设置之前就已经设置了?

如果我从__get 方法中删除&,这将起作用,所以我想这与PHP 所做的引用管理有关。但我希望第三个测试的行为与第二个相同,但事实并非如此。

我真的不明白这一点。任何帮助将不胜感激。

【问题讨论】:

  • 哈哈,这是我们刚刚碰到的东西,虽然令人困惑,但看看 php 在下面是如何工作的非常有趣..

标签: php getter-setter magic-methods pre-increment


【解决方案1】:

结果实际上(经过仔细考虑)是我所期望的。


第一次测试

代码:$o->bar = 2;
输出:Old value: 1 - New value: 2

操作,按顺序:

  • $bar 分配一个新值(调用__set()

显然,这只是放弃了旧值并在其位置上放置了一个新值。没什么复杂的。


第二次测试

代码:$o->bar = $o->bar + 1;
输出:Old value: 2 - New value: 3

操作,按顺序:

  • 获取副本$bar(致电__get()
  • 添加一个
  • $bar 分配一个新值(调用__set()

计算表达式的右侧,并将结果分配给左侧。实例变量$bar 的值在右侧的求值过程中未触及,因此您在__set() 调用开始时会看到它的旧值。


第三次测试

代码:$o->bar += 1;
输出:Old value: 4 - New value: 4

操作,按顺序:

  • 获取参考$bar(致电__get()
  • 添加一个
  • $bar 分配一个新值(调用__set()

这很有趣 - 乍一看,我有点惊讶您看到 __set() 使用此操作生成的输出。因为您正在增加原始 zval 中保存的值,而不是分配一个全新的值 - 因此是一个全新的 zval - 就像前两个一样,看来 __set() 调用是多余的。

然而,当__set() 被调用时,你看到的输出就是你所期望的,因为引用的值 已经增加了之前 __set() 被调用.传递给__set() 的值是递增操作的结果,所以它不会导致另一个被添加到它,它只是将$foo->bar 的值分配给$foo->bar - 所以你会看到旧值和新值一样。


第四次测试

代码:++$o->bar;
输出:Old value: 5 - New value: 5

...在语义上与第三个测试相同。以完全相同的顺序执行完全相同的操作会产生完全相同的输出。再一次,有点奇怪,有任何输出,但你去吧。


我发明的第五个测试

代码:$o->bar++;
输出:Old value: 5 - New value: 6

...在语义上与第二个测试相同。这又很有趣,也有点出乎意料,但它也很有意义,因为 $i++ 返回了 $i 的旧值。


猜测__set() 在第三和第四次测试中被不必要地调用的原因是:

  • (不太可能但可能)因为计算__get() 是否返回引用或副本比简单地调用__set() 更昂贵 - 这似乎不太可能,因为函数调用通常非常昂贵。
  • (更有可能)因为__set() 函数可以包含与实际赋值操作无关的代码,并且如果在更改值时此代码没有运行,则可能会带来严重的不便。例如,如果要实现某种事件驱动的架构,当某些值发生更改时会触发事件,如果这仅适用于直接赋值,那就太烦人了。

【讨论】:

  • 我一直以为第三个测试会翻译成类似于第二个的东西。这种行为让我有些头疼。
  • @Portnoy 解决方案是养成始终使用$var++ 的习惯,除非您需要将新值作为更大操作的一部分 - 只有这样您才使用++$var
  • 是的,但是我正在使用的系统必须支持预增量。我遇到此问题的真实类将对对象所做的更改存储在不同的私有属性中,以便它仅将那些更改的字段推送到数据库。有了这种行为,我无法判断新值是否实际上与前一个不同,所以我目前的方法是在保存对象时更新每个字段,因为预增量会使条件失败。
  • 你可以使用__get()返回的值来实现某种差异功能?所以在__get(),做$this->diff[$varname] = $old,然后在__set()你可以做if ($old != $new || (isset($this->diff[$varname]) && $this->diff[$varname] != $new)) { // $varname has changed } unset($this->diff[$varname]);
  • 有些东西我还不明白。如果我提供的更改$object 属性的唯一方法是通过__set() 方法,那么如何在 设置值之后调用echo?我不相信 PHP 有办法猜测我将把新值放在哪里,设置它,然后调用__set() 方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-28
  • 1970-01-01
  • 2014-01-19
  • 1970-01-01
相关资源
最近更新 更多