In this article I will show a simple way to log every change to doctrine 2 entity.
This one will not be copy / paste. Just check out the code and help yourself.
1. Create event service
/**
* @developer Ivan Gospodinow
*/
namespace Application\Service;
use Application\Entity\DbLogEntity;
use Application\Entity\AbstractEntity;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use \DateTime;
class DoctrineLogChangesService
{
/**
* Hold the changes
* @var type
*/
protected static $rows = [];
protected $sm;
protected $disabled = [
//'Application\Entity\UserEntity',
];
public function __construct($sm)
{
$this->sm = $sm;
$sm->get(‘Application’)
->getEventManager()
->attach(‘finish ’, [$this, 'flush']);
}
public function flush()
{
$orm = $this->sm->get(‘doctrine.entitymanager.orm_default’);
foreach (self::$rows as $row) {
$orm->persist($row);
}
if (!empty(self::$rows)) {
$orm->flush();
}
}
public function getUnitOfWork()
{
return $this->sm->get(‘doctrine.entitymanager.orm_default’)->getUnitOfWork();
}
public function getEntityTableName(LifecycleEventArgs $args)
{
$meta = $args->getObjectManager()->getClassMetadata(get_class($args->getObject()));
return $meta->table['name'];
}
public function getEntityFields(LifecycleEventArgs $args)
{
$meta = $args->getObjectManager()->getClassMetadata(get_class($args->getObject()));
$fields = [];
foreach ($meta->fieldMappings as $field) {
$fields[$field['fieldName']] = $field['columnName'];
}
foreach ($meta->associationMappings as $field) {
if (isset($field['sourceToTargetKeyColumns'])) {
$fields[$field['fieldName']] = key($field['sourceToTargetKeyColumns']);
}
}
return $fields;
}
/**
* Adds all initial fields
* @param LifecycleEventArgs $args
* @return type
*/
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if (in_array(get_class($entity), $this->disabled)) {
return;
}
$tableName = $this->getEntityTableName($args);
$fields = $this->getEntityFields($args);
foreach ($fields as $field => $column) {
$value = call_user_func_array([$entity, 'get' . ucfirst($field)], []);
$row = [
'user' => $this->sm->get('User')->getId(),
'table' => $tableName,
'pk' => $entity->getId(),
'column' => $column,
'oldValue' => null,
'newValue' => $value instanceof AbstractEntity ? $value->getId() : $value
];
$log = new DbLogEntity();
$log->exchangeArray($row);
self::$rows[] = $log;
}
}
/**
* Add only new changes
* @param LifecycleEventArgs $args
* @return type
*/
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if (in_array(get_class($entity), $this->disabled) || !$entity->canUseLifecycleEvents()) {
return;
}
$tableName = $this->getEntityTableName($args);
$fields = $this->getEntityFields($args);
$set = $this->getUnitOfWork()->getEntityChangeSet($entity);
foreach ($set as $property => $changes) {
$oldValue = $changes[0];
$newValue = $changes[1];
if ($changes[0] instanceof AbstractEntity || $changes[1] instanceof AbstractEntity) {
if ($changes[0] instanceof AbstractEntity) {
$oldValue = $changes[0]->getId();
}
if ($changes[1] instanceof AbstractEntity) {
$newValue = $changes[1]->getId();
}
}
if ($oldValue == $newValue) {
continue;
}
$row = [
‘user’ => $this->sm->get(‘User’)->getId(),
‘ table’ => $tableName,
‘ pk’ => $entity->getId(),
‘ column’ => $fields[$property],
‘ oldValue’ => $this->getStringValue($oldValue),
‘ newValue’ => $this->getStringValue($newValue)
];
$log = new DbLogEntity();
$log->exchangeArray($row);
self::$rows[] = $log;
}
}
public function getStringValue($value)
{
if (is_scalar($value)) {
return $value;
}
if ($value instanceof DateTime) {
return $value->format('Y-m-d H:i:s');
} elseif (is_array($value)) {
return serialize($value);
}
return $value;
}
}
2. Add the event service to Doctrine 2
/**
* @developer Ivan Gospodinow
*/
namespace Application;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $e)
{
$application = $e->getApplication();
$sm = $application->getServiceManager();
$doctrineEntityManager = $sm->get('doctrine.entitymanager.orm_default');
$doctrineEventManager = $doctrineEntityManager->getEventManager();
$doctrineEventManager->addEventListener(
[
\Doctrine\ORM\Events::postPersist,
\Doctrine\ORM\Events::preUpdate,
],
new Service\DoctrineLogChangesService($sm)
);
}
}
3. DbLogEntity
/**
* @developer Ivan Gospodinow
*/
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
use \Exception;
/**
* @ORM\Table(name="__dblog")
* @ORM\Entity()
*/
class DbLogEntity
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer", name="id")
*/
protected $id;
/**
* @ORM\Column(type="string", name="user_id")
*/
protected $user;
/**
* @ORM\Column(type="string", name="table_name")
*/
protected $table;
/**
* @ORM\Column(type="integer", name="pk")
*/
protected $pk;
/**
* @ORM\Column(type="string", name="column_name")
*/
protected $column;
/**
* @ORM\Column(type="string", name="old_value")
*/
protected $oldValue;
/**
* @ORM\Column(type="string", name="new_value")
*/
protected $newValue;
/**
* @ORM\Column(type="datetime", name="timestamp")
*/
protected $timestamp;
}
Simple as that.
Suggestions or problems ? Write a comment.