我将 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 protected]"
["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";}
为什么?
这可能与我在使用另一个扩展(可时间戳)时遇到的问题类似,即:学说中使用的默认更改跟踪策略(它尝试自动检测更改)有时会将实体标记为脏,当它们是脏的时不是(对我来说,当我的实体包含日期时间对象时,就会发生这种情况,这是可以理解的,因为这是一个从数据库中提取时需要构造的对象)。这不是一个错误或任何东西 - 这是预期的行为,并且有几种方法可以解决它。
可能值得尝试对要记录的实体实施替代更改跟踪策略,看看是否可以解决问题 - 我猜这种行为(日志记录)不会启动,除非实体状态很脏,您可以通过手动实施更改跟踪来避免:
不要忘记更新您的实体:
YourBundle\Entity\YourThing:
type: entity
table: some_table
changeTrackingPolicy: NOTIFY
请参阅此主题:
https://github.com/Atlantic18/DoctrineExtensions/issues/333#issuecomment-16738878
我今天也遇到这个问题并解决了。 这是完整的解决方案,适用于所有字符串、浮点数、整数和日期时间值。
创建您自己的 LoggableListener 并使用它代替 Gedmo Listener。
<?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;
}
}
对于 Symfony 应用程序,请在 config.yml 文件中注册您的监听器。
stof_doctrine_extensions:
orm:
default:
loggable: true
class:
loggable: MyBundle\Loggable\Listener\MyLoggableListener
如果您在实体中使用日期时间字段,但在数据库中仅存储日期,那么您还需要重置所有设置器中的时间部分。
public function setDateValue(DateTime $dateValue = null)
{
$dateValue->setTime(0, 0, 0);
$this->dateValue = $dateValue;
return $this;
}
这应该可以完成工作。
对于
\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 Method
或 Business Logic
中进行显式转换即可。 例如替换这个(业务逻辑):
$application->setInsuranceNumber($valueComeFromHtmlForm)
这个:
$application->setInsuranceNumber( (int)$valueComeFromHtmlForm)
然后,当您持久化对象时,您将不会在日志中看到任何记录。
我认为这是因为
Loggable
或 Doctrine Change Tracker
期望 Integer
并收到 String (which is a 'not casted Integer')
所以它标志着财产脏了。我们可以在Log Record
中看到它(S
表示新值是String。)