【问题标题】:Gedmo\Loggable logs data that doesn't have changedGedmo\Loggable 记录未更改的数据
【发布时间】:2013-04-20 10:19:42
【问题描述】:

我将 Symfony2.2 与 StofDoctrineExtensionsBundle(以及 Gedmo DoctrineExtensions)一起使用。 我有一个简单的实体

/**
 * @ORM\Entity
 * @Gedmo\Loggable
 * @ORM\Table(name="person")
 */
class Person {
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

[...]

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Assert\NotBlank()
     * @Assert\Date()
     * @Gedmo\Versioned
     */
    protected $birthdate;
}

更改现有对象的属性时,会在表ext_log_entries 中完成一个日志条目。此日志表中的条目仅包含更改的列。我可以通过以下方式阅读日志:

$em = $this->getManager();
$repo = $em->getRepository('Gedmo\Loggable\Entity\LogEntry');
$person_repo = $em->getRepository('Acme\MainBundle\Entity\Person');

$person = $person_repo->find(1);
$log = $repo->findBy(array('objectId' => $person->getId()));
foreach ($log as $log_entry) { var_dump($log_entry->getData()); }

但我不明白的是,为什么字段birthdate 总是包含在日志条目中,即使它没有改变。以下是三个日志条目的一些示例:

array(9) {
  ["salutation"]=>
  string(4) "Herr"
  ["firstname"]=>
  string(3) "Max"
  ["lastname"]=>
  string(6) "Muster"
  ["street"]=>
  string(14) "Musterstraße 1"
  ["zipcode"]=>
  string(5) "00000"
  ["city"]=>
  string(12) "Musterhausen"
  ["birthdate"]=>
  object(DateTime)#655 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["email"]=>
  string(17) "email@example.com"
  ["phone"]=>
  NULL
}

array(2) {
  ["birthdate"]=>
  object(DateTime)#659 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["phone"]=>
  string(9) "123456789"
}

array(2) {
  ["birthdate"]=>
  object(DateTime)#662 (3) {
    ["date"]=>
    string(19) "1893-01-01 00:00:00"
    ["timezone_type"]=>
    int(3)
    ["timezone"]=>
    string(13) "Europe/Berlin"
  }
  ["phone"]=>
  NULL
}

我只想记录真正改变的数据。有没有我还没有看到的选项?这似乎与birthdate 是一个DateTime 对象有关,不是吗?

编辑 它与DateTime 对象无关。这甚至发生在其他实体中。我有另一个包含简单值的实体:

/**
 * @ORM\Entity
 * @Gedmo\Loggable
 * @ORM\Entity(repositoryClass="Acme\MainBundle\Repository\ApplicationRepository")
 * @ORM\Table(name="application")
 */
class Application {

[...]

    /**
     * @ORM\Column(type="integer")
     * @Assert\NotBlank(groups={"FormStepOne", "UserEditApplication"})
     * @Gedmo\Versioned
     */
    protected $insurance_number;
}

当在浏览器中打开编辑表单并保存而不修改时,日志表包含:

update  2013-04-26 11:32:42     Acme\MainBundle\Entity\Application  a:1:{s:16:"insurance_number";s:7:"1234567";}
update  2013-04-26 11:33:17     Acme\MainBundle\Entity\Application  a:1:{s:16:"insurance_number";s:7:"1234567";}

为什么?

【问题讨论】:

    标签: symfony doctrine-orm symfony-2.2 doctrine-extensions stofdoctrineextensions


    【解决方案1】:

    这可能与我在使用这些扩展中的另一个(可时间戳)时遇到的问题类似,即:原则中使用的默认更改跟踪策略(它尝试自动检测更改)有时会将实体标记为脏,当它们不是(对我来说,这是在我的实体包含一个日期时间对象时发生的,这是可以理解的,因为这是一个从数据库中提取它时需要构建的对象)。这不是错误或任何东西 - 这是预期的行为,有几种方法可以解决它。

    可能值得尝试在您要记录的实体上实施替代更改跟踪策略并查看是否可以解决问题 - 我猜想除非实体状态是脏的,否则这种行为(记录)不会启动,这您可以通过手动实施更改跟踪来避免:

    http://docs.doctrine-project.org/en/latest/cookbook/implementing-the-notify-changetracking-policy.html

    不要忘记更新您的实体:

    YourBundle\Entity\YourThing:
        type: entity
        table: some_table
        changeTrackingPolicy: NOTIFY
    

    看到这个帖子:

    https://github.com/Atlantic18/DoctrineExtensions/issues/333#issuecomment-16738878

    【讨论】:

    • 即使这是一个老问题,我还是决定不实现自己的通知功能,让这种糟糕的行为继续下去:(
    • @rabudde 并不是 Doctrine 的错——而是 PHP 的蹩脚对象比较
    • 哦,当然,这也可能是 PHP 的问题,抱歉。但是我无法想象如果我为基于 PHP 的应用程序编写一个包/插件,那么我自己比较对象会那么困难,但我不会侵入 Gedmo 包。不过还是谢谢你的回答。
    【解决方案2】:

    对于\DateTime,我仍在努力,但对于您问题的第二部分,有一种方法可以解决我的Numeric 属性问题:

    /**
     * @ORM\Entity
     * @Gedmo\Loggable
     * @ORM\Entity(repositoryClass="Acme\MainBundle\Repository\ApplicationRepository")
     * @ORM\Table(name="application")
     */
    class Application {
    
    [...]
    
        /**
         * @ORM\Column(type="integer")
         * @Assert\NotBlank(groups={"FormStepOne", "UserEditApplication"})
         * @Gedmo\Versioned
         */
        protected $insurance_number;
    }
    

    在这里您将insurance_number 声明为整数属性,但我们知道PHP 没有类型并且进行动态转换,这与Gedmo Loggable 冲突。

    要解决,只需确保您在Setter MethodBusiness Logic 中进行显式转换。

    例如替换这个(业务逻辑):

    $application->setInsuranceNumber($valueComeFromHtmlForm)

    用这个:

    $application->setInsuranceNumber( (int)$valueComeFromHtmlForm)

    那么当你持久化你的对象时,你不会在日志中看到任何记录。

    我认为这是因为 LoggableDoctrine Change Tracker 期望 Integer 并接收 String (which is a 'not casted Integer') 并且因此它标记属性脏。我们可以在Log Record 中看到(S 表示新值是String。)

    【讨论】:

      【解决方案3】:

      我今天也遇到了这个问题,解决了。 这是完整的解决方案,适用于所有字符串、浮点数、int 和 DateTime 值。

      1. 制作你自己的 LoggableListener 并使用它来代替 Gedmo 监听器。

        <?php
        
        namespace MyBundle\Loggable\Listener;
        
        use Gedmo\Loggable\LoggableListener;
        use Gedmo\Tool\Wrapper\AbstractWrapper;
        
        class MyLoggableListener extends LoggableListener
        {
            protected function getObjectChangeSetData($ea, $object, $logEntry)
            {
                $om = $ea->getObjectManager();
                $wrapped = AbstractWrapper::wrap($object, $om);
                $meta = $wrapped->getMetadata();
                $config = $this->getConfiguration($om, $meta->name);
                $uow = $om->getUnitOfWork();
                $values = [];
        
                foreach ($ea->getObjectChangeSet($uow, $object) as $field => $changes) {
                    if (empty($config['versioned']) || !in_array($field, $config['versioned'])) {
                        continue;
                    }
        
                    $oldValue = $changes[0];
                    if ($meta->isSingleValuedAssociation($field) && $oldValue) {
                        if ($wrapped->isEmbeddedAssociation($field)) {
                            $value = $this->getObjectChangeSetData($ea, $oldValue, $logEntry);
                        } else {
                            $oid = spl_object_hash($oldValue);
                            $wrappedAssoc = AbstractWrapper::wrap($oldValue, $om);
                            $oldValue = $wrappedAssoc->getIdentifier(false);
                            if (!is_array($oldValue) && !$oldValue) {
                                $this->pendingRelatedObjects[$oid][] = [
                                    'log' => $logEntry,
                                    'field' => $field,
                                ];
                            }
                        }
                    }
        
                    $value = $changes[1];
                    if ($meta->isSingleValuedAssociation($field) && $value) {
                        if ($wrapped->isEmbeddedAssociation($field)) {
                            $value = $this->getObjectChangeSetData($ea, $value, $logEntry);
                        } else {
                            $oid = spl_object_hash($value);
                            $wrappedAssoc = AbstractWrapper::wrap($value, $om);
                            $value = $wrappedAssoc->getIdentifier(false);
                            if (!is_array($value) && !$value) {
                                $this->pendingRelatedObjects[$oid][] = [
                                    'log' => $logEntry,
                                    'field' => $field,
                                ];
                            }
                        }
                    }
        
                    //fix for DateTime, integer and float entries
                    if ($value == $oldValue) {
                        continue;
                    }
        
                    $values[$field] = $value;
                }
        
                return $values;
            }
        }
        
      2. 对于 Symfony 应用程序,在 config.yml 文件中注册您的监听器。

        stof_doctrine_extensions:
            orm:
                default:
                    loggable: true
            class:
                loggable: MyBundle\Loggable\Listener\MyLoggableListener
        
      3. 如果您在实体中使用 DateTime 字段,但在数据库中只存储日期,那么您还需要在所有设置器中重置时间部分。

        public function setDateValue(DateTime $dateValue = null)
        {
            $dateValue->setTime(0, 0, 0);
            $this->dateValue = $dateValue;
            return $this;
        }
        

      这应该可以完成工作。

      【讨论】:

        猜你喜欢
        • 2017-11-09
        • 2022-10-17
        • 2015-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-16
        相关资源
        最近更新 更多