【问题标题】:Check if an object has changed检查对象是否已更改
【发布时间】:2015-02-24 08:36:56
【问题描述】:

是否有一种更原生的方式(例如内置函数),使用更少的用户空间代码来检查对象属性值是否已更改,而不是使用其中一种方法:

序列化方法

$obj = new stdClass(); // May be an instance of any class

echo $hashOld = md5(serialize($obj)) . PHP_EOL;

$obj->change = true;

echo $hashNew = md5(serialize($obj)) . PHP_EOL;

echo 'Changed: '; var_dump($hashOld !== $hashNew);

结果:

f7827bf44040a444ac855cd67adfb502 (initial)
506d1a0d96af3b9920a31ecfaca7fd26 (changed)
Changed: bool(true)

卷影复制方法

$obj = new stdClass();
$shadowObj = clone $obj;

$obj->change = true;

var_dump($shadowObj != $obj);

结果:

bool(true);

这两种方法都有效。但与非用户态实现相比,两者都有缺点。第一个需要 CPU 进行序列化和散列,第二个需要内存来存储克隆。并且某些类可能无法克隆。

PHP 不跟踪对象属性的变化吗? PHP 没有公开一个方法来使用它吗?

【问题讨论】:

  • 对于用户空间对象(与 stdClass 对象相反)我会使用观察者
  • 您如何定义“已更改”您的意思是属性值的读取方式不同,还是某些代码明确设置了属性?还是别的什么?
  • @deceze 你能解释一下“脏检查”是什么意思吗?那么对你来说什么是“干净的检查”呢?
  • 你可以强制使用setter方法而不是直接赋值。这些方法可以跟踪变化。
  • 你是对的,但是我不能使用 e.x. \stdClass。而且我必须将逻辑放入“愚蠢的”getter/setter 方法中。然后我更喜欢一个卷影副本,我可以通过反射检查当前对象的任何更改。

标签: php


【解决方案1】:

你想做什么?

您正在尝试将对象与自身进行比较,经过一些“未知”操作链来检查对象是否已更改。如果这是真的,那么有一些逻辑要点需要观察。一开始,如果你想比较对象和它自己,你只有两个选择:

  1. 记住整个对象的状态(例如哈希,或者只是复制整个对象)
  2. 跟踪一段时间内的变化

没有其他合乎逻辑的方法。比较内存分配、真实对象、复制对象、比较哈希值都是第一点。跟踪更改,保存对象内部的更改,记住同时操作,在第 2 点内。

所以在我看来,这个问题有点支持数据问题。在那种情况下,有很多很多解决方案,但就我而言,它们都没有在 php 中硬编码。为什么?


答案很简单。 PHP 家伙遇到了与您相同的问题 :)。 因为如果这将在 php 中被硬编码,那么 php 应该运行/使用这些机制之一(1)或(2)。

在这种情况下,你创建的每一个对象,你所做的每一个操作都应该写在某个地方,以记住每一个状态/对象/某事,并在将来使用它们进行比较。

虽然您需要此解决方案,但几乎 ~100% 的网站不需要。所以在 php 中硬编码会使~100% 的网站工作更慢,而你的工作更快;)。


PHP 假设解决方案?

我能想到的唯一解决方案(将来可能会内置php)是制作某种php config标志:track objects,并且只有这个标志是true,然后运行所有的php机制跟踪对象状态。 但这也意味着巨大的性能差距。因为所有的ifs(如果跟踪,如果跟踪,如果跟踪)也需要处理器和内存时间。

还有一个问题,要比较什么? 您需要将对象与相同对象进行比较,但是...几分钟前?前几次手术?不...您必须在代码中准确指向一个位置,然后在代码中指向第二个位置并比较这两个位置的对象。 所以假设的自动跟踪是......有点无能为力,因为在 对象状态的时间数组中没有“键”。我的意思是,即使你有 @987654328 @函数,它应该是什么样子的?

<?php

    function magic_object_comparer() {} // Arguments??
    function magic_object_comparer($object_before, $object_after) {} // you must save object_before somewhere...??
    function magic_object_comparer($object, $miliseconds) {} // How many miliseconds?
    function magic_object_comparer($object, $operations) {} // How many operations? Which operations?


    magic_comparer_start($object);
    // ... Few operations...
    $boolean = magic_comparer_compare_from start($object);
    // Same as own implementation...
?>

遗憾的是,您只剩下自己的实现了...

毕竟,我建议为此实现某种自己的机制,并且记住只在需要的地方使用它。因为这种机制肯定会耗费时间和内存。所以请仔细考虑:

  1. 您要比较哪些对象。为什么?
  2. 何时比较它们?
  3. 是否需要比较所有更改?
  4. 保存这些状态更改的最简单方法是什么?

毕竟,尝试实现它。我看到你有丰富的 php 知识,所以我很确定你会想出一些东西。 这个问题和讨论中也有很多cmet,和可能的想法。


但毕竟也许我解释了一点为什么,没有内置解决方案,以及为什么将来不应该有一个......:)。


更新

看看这里:http://www.fluffycat.com/PHP-Design-Patterns/。这是关于 php 模式的一个很好的资源。您应该查看 adapterdecoratorobserver 模式,以获得可能的优雅的面向对象的解决方案。

【讨论】:

  • 非常感谢您对“问题”的看法。我想你明白了为什么没有内置解决方案。是的,我很高兴有这么多其他方法和想法。我也有一些关于使用反射的想法,直到现在还没有提到。我肯定会花时间研究在 PHP 中跟踪对象,也许会想出一个好的库来轻松、灵活地完成它并尽可能提高性能
  • 感谢接受 :)。我很高兴我帮了一点忙。请看一下,我更新了我的答案。我能想到的最好的解决方案是用魔法方法覆盖所有可能的方法,它跟踪对象内部的变化,并立即运行原始调用的方法。或者将您的对象“打包”到另一个用于跟踪的对象中,并且从外部与原始对象没有区别。
  • 看看这个:stackoverflow.com/questions/10704365/…。这个问题不相关,但是将一个对象存储到另一个对象中的想法是存在的。在这种情况下,Foo 是监视您的对象的容器,而 bar 是一个对象。每个 Bar 方法都必须在 Foo 对象中神奇地重新定义。您要跟踪的每个对象都有自己的容器,而其他所有对象都没有。我认为这是一个简单而优雅的解决方案......
  • 一个包含大量资源和可能解决方案的更新:适配器、装饰器和观察者模式。
【解决方案2】:

虽然我也在寻找一种非常快速/更快的方法,但实际上我使用的是方法 2 的一种变体。这种方法的优点是它(相当)快(与 isset() 相比),具体取决于对象大小。而且您不必记住每次更改对象时都设置-&gt;modified 属性。

global $shadowcopy; // just a single copy in this simple example.
$thiscopy = (array) $obj; // don't use clone.
if ($thiscopy !== $shadowcopy) {
// it has been modified
// if you want to know if a field has been added use array_diff_key($thiscopy,$shadowcopy);

}
$shadowcopy = $thiscopy; // if you don't modify thiscopy or shadowcopy, it will be a reference, so an array copy won't be triggered.

这基本上是方法 2,但没有克隆。如果您的属性值是另一个对象 (vobj),则可能需要 clone(否则两个引用将指向同一个对象),但值得注意的是 您想要的对象 vobj看看上面的代码是否改变了。关于 clone 的事情是它正在构造第二个对象(类似的性能),但是如果您想查看更改了哪些值,则不关心对象本身,只关心值。并且对象的数组转换非常快(大约是布尔转换的速度的 2 倍)......好吧,直到大型对象。此外,直接数组比较 === 非常快,对于 100 vals 以下的数组。

我很确定存在一种更快的方法...

【讨论】:

    【解决方案3】:

    我可以为您提供另一种解决问题的方法,实际上要检测“对象是否已更改”,我们可以使用observer pattern 设计原则。对于某些想要获得有关对象更改的通知的人来说,这种方式应该会更好。

    合同/ISubject.php

    <?php
    
    namespace Contracts;
    
    interface ISubject
    {
        public function attach($observer): void;
        public function detach($observer): void;
        public function notify(): void;
    }
    

    合同/IObserver.php

    <?php
    
    namespace Contracts;
    
    interface IObserver
    {
        public function update($subject);
    }
    

    主题.php

    class Subject implements ISubject
    {
        public $state; // That is detector 
    
        private $observers;
    
        public function __construct()
        {
            $this->observers = new \SplObjectStorage(); // That is php built in object for testing purpose I use SplObjectStorage() to store attach()'ed objects.
        }
    
        public function attach($observer): void
        {
            echo "Subject: Attached an observer.\n";
            $this->observers->attach($observer);
        }
    
        public function detach($observer): void
        {
            $this->observers->detach($observer);
            echo "Subject: Detached an observer.\n";
        }
    
        public function notify(): void
        {
            echo "Subject: Notifying observers...\n";
            foreach ($this->observers as $observer) {
                $observer->update($this);
            }
        }
    
        public function someYourLogic()
        {
            $this->state = rand(0, 10);
            echo "Subject: My state has just changed to: {$this->state}\n";
            $this->notify();
        }
    }
    

    Observer1.php |另外,您可以拥有任意数量的 ConcreteObserver

    class Observer1 implements IObserver
    {
        public function update($subject): void
        {
            if ($subject->state < 5) {
                echo "Observer1: Reacted to the event.\n";
            }
        }
    }
    

    Clinet.php

    $subject = new Subject();
    
    $o1 = new Observer1();
    $subject->attach($o1);
    $subject->someYourLogic();
    

    【讨论】:

      【解决方案4】:

      恐怕没有内置方法。卷影复制方法是最好的方法。

      如果你可以控制类,一个更简单的方法是添加一个modified 变量:

      $this->modified = false;
      

      当我以任何方式修改对象时,我只是使用

      $obj->modified = true;
      

      这样我以后可以检查

      if($obj->modified){ // Do Something
      

      检查它是否被修改。请记住在将内容保存到数据库之前unset($obj-&gt;modified)

      【讨论】:

      • 老实说,这是一个关于如何跟踪对象更改的非常简单的“初学者”想法。但问题与比较它们有关。如果您更改了一个对象,然后再次更改它并保存在其他位置,并且再次想要比较第二个和第三个对象怎么办?我的意思是,这个想法是可以的,但它并没有回答这个问题。但它可能对其他人有用,所以请不要投反对票:)。
      • @JacekKowalewski 简单就是这个想法。卷影复制方法是最好的选择。
      • 感谢您的回答 :-)。实际上,这可以回答我的问题,但不幸的是,它取决于您可以在\stdClass 中执行的“动态生成属性”,或者您必须在每个您喜欢跟踪的类中包含这样一个公共属性,并且必须牢记手动设置状态。如果类将 __clone() 方法声明为私有,则卷影副本不可用。
      • 这就是为什么,正如上面所建议的,setter 被发明了。 stdClass 只是一种轻松构建数据以传递的方式,它不会有复杂的行为。
      • @TiMESPLiNTER 与卷影复制方法类似,您可以从类中克隆一些变量。例如,如果只有 $obj-&gt;foo$obj-&gt;bar 可以更改,请使用 $objOld = array($obj-&gt;foo, $obj-&gt;bar) 稍后对对象执行相同操作,然后评估 array_diff
      【解决方案5】:

      我们可以在没有观察者的情况下实现它。

      对于纯php,如果需要,我们可以使用$attributes & $original 来检查check this explanation 修改了什么。

      $modifiedValues = [];
      foreach($obj->attributes as $column=>$value) {
          if(!array_key_exists($column, $obj->original) || $obj->original[$column] != $value) {
              $modifiedValues[$column] = $value;
          }
      }
      // then check $modifiedValues if it contains values
      

      对于 Laravel 用户,我们可以使用 isDirty() 方法。其用法:

      $user = App\User::first();
      $user->isDirty();          //false
      $user->name = "Peter";
      $user->isDirty();          //true
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-10
        • 2017-11-25
        • 1970-01-01
        • 2020-11-18
        • 2013-06-14
        • 2018-09-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多