src/ZF2EntityAudit/EventListener/LogRevisionsListener.php
<?php
namespace ZF2EntityAudit\EventListener;
use ZF2EntityAudit\Audit\Manager;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
class LogRevisionsListener implements EventSubscriber
{
/**
* @var \ZF2EntityAudit\Audit\Configuration
*/
private $config;
/**
* @var \ZF2EntityAudit\Metadata\MetadataFactory
*/
private $metadataFactory;
/**
* @var Doctrine\DBAL\Connection
*/
private $conn;
/**
* @var Doctrine\DBAL\Platforms\AbstractPlatform
*/
private $platform;
/**
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var array
*/
private $insertRevisionSQL = array();
/**
* @var Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var int
*/
private $revisionId;
public function __construct(Manager $auditManager)
{
$this->config = $auditManager->getConfiguration();
$this->metadataFactory = $auditManager->getMetadataFactory();
}
public function getSubscribedEvents()
{
return array(Events::onFlush, Events::postPersist, Events::postUpdate);
}
public function postPersist(LifecycleEventArgs $eventArgs)
{
// onFlush was executed before, everything already initialized
$entity = $eventArgs->getEntity();
$class = $this->em->getClassMetadata(get_class($entity));
if (!$this->metadataFactory->isAudited($class->name)) {
return;
}
$this->saveRevisionEntityData($class, $this->getOriginalEntityData($entity), 'INS');
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
// onFlush was executed before, everything already initialized
$entity = $eventArgs->getEntity();
$class = $this->em->getClassMetadata(get_class($entity));
if (!$this->metadataFactory->isAudited($class->name)) {
return;
}
$entityData = array_merge($this->getOriginalEntityData($entity), $this->uow->getEntityIdentifier($entity));
$this->saveRevisionEntityData($class, $entityData, 'UPD');
}
public function onFlush(OnFlushEventArgs $eventArgs)
{
$this->em = $eventArgs->getEntityManager();
$this->conn = $this->em->getConnection();
$this->uow = $this->em->getUnitOfWork();
$this->platform = $this->conn->getDatabasePlatform();
$this->revisionId = null; // reset revision
foreach ($this->uow->getScheduledEntityDeletions() AS $entity) {
$class = $this->em->getClassMetadata(get_class($entity));
if (!$this->metadataFactory->isAudited($class->name)) {
continue;
}
$entityData = array_merge($this->getOriginalEntityData($entity), $this->uow->getEntityIdentifier($entity));
$this->saveRevisionEntityData($class, $entityData, 'DEL');
}
}
/**
* get original entity data, including versioned field, if "version" constraint is used
*
* @param mixed $entity
* @return array
*/
public function getOriginalEntityData($entity)
{
$class = $this->em->getClassMetadata(get_class($entity));
$data = $this->uow->getOriginalEntityData($entity);
if ($class->isVersioned) {
$versionField = $class->versionField;
$data[$versionField] = $class->reflFields[$versionField]->getValue($entity);
}
return $data;
}
private function getRevisionId()
{
if ($this->revisionId === null) {
$date = date_create("now")->format($this->platform->getDateTimeFormatString());
if ($this->config->getCurrentUser() != null) {
$userId = $this->config->getCurrentUser()->getId();
} else {
$userId = null;
}
$this->conn->insert($this->config->getRevisionTableName(), array(
'timestamp' => $date,
'user_id' => $userId,
'note' => $this->config->getNote(),
'ipaddress' => $this->config->getIpAddress()
));
$sequenceName = $this->platform->supportsSequences()
? 'REVISIONS_ID_SEQ'
: null;
$this->revisionId = $this->conn->lastInsertId($sequenceName);
}
return $this->revisionId;
}
private function getInsertRevisionSQL($class)
{
if (!isset($this->insertRevisionSQL[$class->name])) {
$placeholders = array('?', '?');
$tableName = $this->config->getTablePrefix() . $class->table['name'] . $this->config->getTableSuffix();
$sql = "INSERT INTO " . $tableName . " (" .
$this->config->getRevisionFieldName() . ", " . $this->config->getRevisionTypeFieldName();
foreach ($class->fieldNames AS $field) {
$type = Type::getType($class->fieldMappings[$field]['type']);
$placeholders[] = (!empty($class->fieldMappings[$field]['requireSQLConversion']))
? $type->convertToDatabaseValueSQL('?', $this->platform)
: '?';
$sql .= ', ' . $class->getQuotedColumnName($field, $this->platform);
}
foreach ($class->associationMappings AS $assoc) {
if ( ($assoc['type'] & ClassMetadata::TO_ONE) > 0 && $assoc['isOwningSide']) {
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
$sql .= ', ' . $sourceCol;
$placeholders[] = '?';
}
}
}
$sql .= ") VALUES (" . implode(", ", $placeholders) . ")";
$this->insertRevisionSQL[$class->name] = $sql;
}
return $this->insertRevisionSQL[$class->name];
}
/**
* @param ClassMetadata $class
* @param array $entityData
* @param string $revType
*/
public function saveRevisionEntityData($class, $entityData, $revType)
{
$params = array($this->getRevisionId(), $revType);
$types = array(\PDO::PARAM_INT, \PDO::PARAM_STR);
foreach ($class->fieldNames AS $field) {
$params[] = $entityData[$field];
$types[] = $class->fieldMappings[$field]['type'];
}
foreach ($class->associationMappings AS $field => $assoc) {
if (($assoc['type'] & ClassMetadata::TO_ONE) > 0 && $assoc['isOwningSide']) {
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
if ($entityData[$field] !== null) {
$relatedId = $this->uow->getEntityIdentifier($entityData[$field]);
}
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) {
if ($entityData[$field] === null) {
$params[] = null;
$types[] = \PDO::PARAM_STR;
} else {
$params[] = $relatedId[$targetClass->fieldNames[$targetColumn]];
$types[] = $targetClass->getTypeOfColumn($targetColumn);
}
}
}
}
$this->conn->executeUpdate($this->getInsertRevisionSQL($class), $params, $types);
}
public function setEntityManager($entityManager)
{
$this->em = $entityManager;
$this->uow = $entityManager->getUnitOfWork();
$this->conn = $this->em->getConnection();
$this->platform = $this->conn->getDatabasePlatform();
return $this;
}
}