原则 2 - 记录多对多关系中的变化
我使用 Loggable 行为扩展 来记录我的实体.我也想记录多对多关系的变化.我想向用户展示这种更改日志:
I use Loggable behavioral extension to log changes in my entities. I want to log changes in manyToMany relations too. I want to show to user this kind of change log:
+--------------------------------------------------+
| Article "My Article" change log: |
+-------+------------+-----------------------------+
| Who | When | What |
+-------+------------+-----------------------------+
| Admin | 2015-07-01 | Removed tags "tag1", "tag2" |
| Admin | 2015-07-01 | Added tags "tag3" |
+-------+------------+-----------------------------+
事件问题
我认为,当多对多关系发生变化时,Doctrine 不会触发事件,所以 Loggable(侦听学说事件)不保存日志条目.我可以通过创建我自己的 manyToMany 表来解决这个问题,但这是第二个问题:
Event problem
I think, Doctrine doesn't fire events when manyToMany relation changes, so Loggable (listening doctrine events) doesn't save log entry. I can work around it by creating my own manyToMany table, but here's go the second problem:
当我在没有@JoinTable 注释的情况下创建代表 manyToMany 关系的实体时,我不知道如何编写新实体以使其表现得像旧的 JoinTable 实体.我不想要 BC 休息.你能给我一个线索,Doctrine 如何处理这个问题?
When I create entity representing manyToMany relation without @JoinTable annotation, I don't know, how to write the new entity to behave like the old JoinTable one. I want no BC break. Can you give me a clue, how Doctrine handles this?
您有什么建议,如何记录多对多关系的变化?
Do you have any recommendation, how to log changes in manyToMany relations?
推荐答案
无需创建自己的连接表的解决方案.
Solution without creating your own join tables.
我已经修改了我创建的 LoggableListener 来覆盖 Gedmo LoggableListener,我的版本可以工作,玩这个直到你开始工作.
I have modified the LoggableListener that I created to override the Gedmo LoggableListener, my version works, play around with this till you get it working.
基本上,用你自己的版本扩展 Gedmo LoggableListener 并覆盖/添加一些修改过的函数:
Basically, extend the Gedmo LoggableListener with your own version and override /add a few modified functions:
prePersistLogEntry 已启用以允许您根据需要修改 logEntry.我的 logEntry 实体包含一个用户实体和用户全名而不是他们的用户名.
prePersistLogEntry is enabled to allow you to modify the logEntry if you want to. My logEntry entities contain a user entity and the users Full Name instead of their username.
getCollectionsChangeSetData 是一个新函数,用于提取集合并访问 Doctrine PersistentCollections 方法.[http://www.doctrine-project.org/api/orm/2.1/class-Doctrine.ORM.PersistentCollection.html]
getCollectionsChangeSetData is a new function to extract the collection and get access to the Doctrine PersistentCollections methods. [http://www.doctrine-project.org/api/orm/2.1/class-Doctrine.ORM.PersistentCollection.html]
stripCollectionArray 新函数,用于从集合实体中提取所需的信息并将它们插入到 php 数组中以持久化到 LogEntry.
stripCollectionArray new function to extract the desired information from the collection entities and insert them into a php array for persisting to the LogEntry.
有关信息,如果您计划使用 Loggable 准则扩展的恢复功能,那么您还需要扩展和覆盖 LogEntryRepository 中的恢复方法.当前的 revert 方法不会从保存在 LogEntry 中的 ManyToMany 关联中识别 id.这就是 stripCollectionArray 函数还将id"和class"值保存到 LogEntry 的原因.
For information, if you are planning to user the revert functionality of the Loggable doctrine extension then you will also need to extend and override the revert method in the LogEntryRepository. The current revert method will not recognise the id from the ManyToMany associations saved in the LogEntry. That is why stripCollectionArray function also saves the 'id' and 'class' values to the LogEntry.
祝你好运.
<?php
namespace AppBundleListener;
use DoctrineCommonEventArgs;
use GedmoLoggableMappingEventLoggableAdapter;
use GedmoToolWrapperAbstractWrapper;
use GedmoLoggableLoggableListener as GedmoLoggableListener;
use SymfonyComponentSecurityCoreAuthenticationTokenStorageTokenStorageInterface;
use AppBundleEntityClause;
use AppBundleEntityGuidanceNote;
use AppBundleEntityStandard;
use GedmoLoggableEntityMappedSuperclassAbstractLogEntry;
use DoctrineORMPersistentCollection;
/**
* Loggable listener
*
* Extends the Gedmo loggable listener to provide some custom functionality.
*
*
* @author Mark Ogilvie <mark.ogilvie@specshaper.com>
*/
class LoggableListener extends GedmoLoggableListener {
// Token storage to get user
private $tokenStorage;
// Injet token storage in the services.yml
public function __construct(TokenStorageInterface $token) {
$this->tokenStorage = $token;
}
/**
* Manipulate the LogEntry entity prior to persisting.
* In this case add a user, and set entity information
* according to the custom entity family group.
*
* @param EventArgs $eventArgs
*
* @return void
*/
protected function prePersistLogEntry($logEntry, $object) {
$user = $this->tokenStorage->getToken()->getUser();
$logEntry instanceof AbstractLogEntry;
$logEntry
->setUser($user)
->setChangedObject('text.default')
->setUsername($user->getFullName())
;
switch (true) {
case $object instanceof Clause:
$logEntry->setChangedObject('text.clause')
->setEntity($object)
;
break;
case $object instanceof GuidanceNote:
$logEntry->setChangedObject('text.guidanceNote')
->setEntity($object->getClause())
;
break;
case $object instanceof Standard:
$logEntry->setChangedObject('text.standard')
;
break;
}
}
/**
* Returns an objects changeset data
*
* Modified to create an array which has old and new values instead
* of just the new.
*
* Also added reference to UoW collection changes to pick up ManyToMany
* relationships
*
* @param LoggableAdapter $ea
* @param object $object
* @param object $logEntry
*
* @return array
*/
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();
// Define an array to return as the change set data.
$returnArray = array();
foreach ($ea->getObjectChangeSet($uow, $object) as $field => $changes) {
if (empty($config['versioned']) || !in_array($field, $config['versioned'])) {
continue;
}
$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][] = array(
'log' => $logEntry,
'field' => $field,
);
}
}
}
$returnArray[$field]['previous'] = $changes[0];
$returnArray[$field]['new'] = $value;
}
// For each collection add it to the return array in our custom format.
foreach ($uow->getScheduledCollectionUpdates() as $col) {
$associations = $this->getCollectionChangeSetData($col);
$returnArray = array_merge($returnArray, $associations);
}
return $returnArray;
}
/**
* New custom function to get information about changes to entity relationships
* Use the PersistentCollection methods to extract the info you want.
*
* @param PersistentCollection $col
* @return array
*/
private function getCollectionChangeSetData(PersistentCollection $col) {
$fieldName = $col->getMapping()['fieldName'];
// http://www.doctrine-project.org/api/orm/2.1/class-Doctrine.ORM.PersistentCollection.html
// $col->toArray() returns the onFlush array of collection items;
// $col->getSnapshot() returns the prePersist array of collection items
// $col->getDeleteDiff() returns the deleted items
// $col->getInsertDiff() returns the inserted items
// These methods return persistentcollections. You need to process them to get just the title/name
// of the entity you want.
// Instead of creating two records, you can create an array of added and removed fields.
// Use private a newfunction stripCollectionArray to process the entity into the array
$newValues[$fieldName]['new'] = $this->stripCollectionArray($col->toArray());
$newValues[$fieldName]['previous'] = $this->stripCollectionArray($col->getSnapshot());
return $newValues;
}
/**
* Function to process your entity into the desired format for inserting
* into the LogEntry
*
* @param type $entityArray
* @return type
*/
private function stripCollectionArray($entityArray) {
$returnArr = [];
foreach ($entityArray as $entity) {
$arr = [];
$arr['id'] = $entity->getId();
$arr['class'] = get_class($entity);
if (method_exists($entity, 'getName')) {
$arr['name'] = $entity->getName();
} elseif (method_exists($entity, 'getTitle')) {
$arr['name'] = $entity->getTitle();
} else {
$arr['name'] = get_class($entity);
}
$returnArr[] = $arr;
}
return $returnArr;
}
}
相关文章