浏览代码

[loggable] single listener through event adapter

gediminasm 14 年之前
父节点
当前提交
35bf0721e1

+ 192 - 62
lib/Gedmo/Loggable/LoggableListener.php

@@ -2,13 +2,15 @@
 
 
 namespace Gedmo\Loggable;
 namespace Gedmo\Loggable;
 
 
-use Gedmo\Loggable\AbstractLoggableListener,
-    Doctrine\Common\Persistence\ObjectManager,
+use Doctrine\Common\Persistence\ObjectManager,
     Doctrine\Common\Persistence\Mapping\ClassMetadata,
     Doctrine\Common\Persistence\Mapping\ClassMetadata,
-    Doctrine\ORM\Events,
+    Gedmo\Mapping\MappedEventSubscriber,
+    Gedmo\Mapping\Event\AdapterInterface,
     Doctrine\Common\EventArgs;
     Doctrine\Common\EventArgs;
 
 
 /**
 /**
+ * Loggable listener
+ *
  * @author Boussekeyt Jules <jules.boussekeyt@gmail.com>
  * @author Boussekeyt Jules <jules.boussekeyt@gmail.com>
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  * @package Gedmo.Loggable
  * @package Gedmo.Loggable
@@ -16,111 +18,239 @@ use Gedmo\Loggable\AbstractLoggableListener,
  * @link http://www.gediminasm.org
  * @link http://www.gediminasm.org
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
  */
-class LoggableListener extends AbstractLoggableListener
+class LoggableListener extends MappedEventSubscriber
 {
 {
     /**
     /**
-     * The default LogEntry class used to store the logs
+     * Create action
+     */
+    const ACTION_CREATE = 'create';
+
+    /**
+     * Update action
+     */
+    const ACTION_UPDATE = 'update';
+
+    /**
+     * Remove action
+     */
+    const ACTION_REMOVE = 'remove';
+
+    /**
+     * Username for identification
      *
      *
      * @var string
      * @var string
      */
      */
-    protected $defaultLogEntryEntity = 'Gedmo\Loggable\Entity\LogEntry';
+    protected $username;
 
 
     /**
     /**
-     * {@inheritdoc}
+     * List of log entries which do not have the foreign
+     * key generated yet - MySQL case. These entries
+     * will be updated with new keys on postPersist event
+     *
+     * @var array
      */
      */
-    public function getSubscribedEvents()
-    {
-        return array(
-            Events::onFlush, 
-            Events::loadClassMetadata,
-            Events::postPersist
-        );
-    }
+    private $pendingLogEntryInserts = array();
 
 
     /**
     /**
-     * {@inheritdoc}
+     * For log of changed relations we use
+     * its identifiers to avoid storing serialized Proxies.
+     * These are pending relations in case it does not
+     * have an identifier yet
+     *
+     * @var array
      */
      */
-    protected function getLogEntryClass(array $config, $class)
-    {
-        return isset($this->configurations[$class]['logEntryClass']) ?
-            $this->configurations[$class]['logEntryClass'] : 
-            $this->defaultLogEntryEntity;
-    }
+    private $pendingRelatedObjects = array();
 
 
     /**
     /**
-     * {@inheritdoc}
+     * Set username for identification
+     *
+     * @param mixed $username
      */
      */
-    protected function getObjectManager(EventArgs $args)
+    public function setUsername($username)
     {
     {
-        return $args->getEntityManager();
+        if (is_string($username)) {
+            $this->username = $username;
+        } elseif (is_object($username) && method_exists($username, 'getUsername')) {
+            $this->username = (string)$username->getUsername();
+        } else {
+            throw new \Gedmo\Exception\InvalidArgumentException("Username must be a string, or object should have method: getUsername");
+        }
     }
     }
 
 
     /**
     /**
      * {@inheritdoc}
      * {@inheritdoc}
      */
      */
-    protected function getScheduledObjectUpdates($uow)
+    public function getSubscribedEvents()
     {
     {
-        return $uow->getScheduledEntityUpdates();
+        return array(
+            'onFlush',
+            'loadClassMetadata',
+            'postPersist'
+        );
     }
     }
 
 
     /**
     /**
-     * {@inheritdoc}
+     * Get the LogEntry class
+     *
+     * @param AdapterInterface $ea
+     * @param string $class
+     * @return string
      */
      */
-    protected function getScheduledObjectInsertions($uow)
+    protected function getLogEntryClass(AdapterInterface $ea, $class)
     {
     {
-        return $uow->getScheduledEntityInsertions();
+        return isset($this->configurations[$class]['logEntryClass']) ?
+            $this->configurations[$class]['logEntryClass'] :
+            $ea->getDefaultLogEntryClass();
     }
     }
 
 
     /**
     /**
-     * {@inheritdoc}
+     * Mapps additional metadata
+     *
+     * @param EventArgs $eventArgs
+     * @return void
      */
      */
-    protected function getScheduledObjectDeletions($uow)
+    public function loadClassMetadata(EventArgs $eventArgs)
     {
     {
-        return $uow->getScheduledEntityDeletions();
+        $ea = $this->getEventAdapter($eventArgs);
+        $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->getClassMetadata());
     }
     }
-    
+
     /**
     /**
-     * {@inheritdoc}
+     * Checks for inserted object to update its logEntry
+     * foreign key
+     *
+     * @param EventArgs $args
+     * @return void
      */
      */
-    protected function getObjectChangeSet($uow, $object)
+    public function postPersist(EventArgs $args)
     {
     {
-        return $uow->getEntityChangeSet($object);
+        $ea = $this->getEventAdapter($args);
+        $object = $ea->getObject();
+        $om = $ea->getObjectManager();
+        $oid = spl_object_hash($object);
+        $uow = $om->getUnitOfWork();
+        if ($this->pendingLogEntryInserts && array_key_exists($oid, $this->pendingLogEntryInserts)) {
+            $meta = $om->getClassMetadata(get_class($object));
+            $config = $this->getConfiguration($om, $meta->name);
+            // there should be single identifier
+            $identifierField = $ea->getSingleIdentifierFieldName($meta);
+            $logEntry = $this->pendingLogEntryInserts[$oid];
+            $logEntryMeta = $om->getClassMetadata(get_class($logEntry));
+
+            $id = $meta->getReflectionProperty($identifierField)->getValue($object);
+            $logEntryMeta->getReflectionProperty('objectId')->setValue($logEntry, $id);
+            $uow->scheduleExtraUpdate($logEntry, array(
+                'objectId' => array(null, $id)
+            ));
+            unset($this->pendingLogEntryInserts[$oid]);
+        }
+        if ($this->pendingRelatedObjects && array_key_exists($oid, $this->pendingRelatedObjects)) {
+            $identifiers = $ea->extractIdentifier($om, $object, false);
+            $props = $this->pendingRelatedObjects[$oid];
+
+            $logEntry = $props['log'];
+            $logEntryMeta = $om->getClassMetadata(get_class($logEntry));
+            $oldData = $data = $logEntry->getData();
+            $data[$props['field']] = $identifiers;
+            $logEntry->setData($data);
+
+            $uow->scheduleExtraUpdate($logEntry, array(
+                'data' => array($oldData, $data)
+            ));
+            unset($this->pendingRelatedObjects[$oid]);
+        }
     }
     }
-    
+
     /**
     /**
-     * {@inheritdoc}
+     * Looks for loggable objects being inserted or updated
+     * for further processing
+     *
+     * @param EventArgs $args
+     * @return void
      */
      */
-    protected function getSingleIdentifierFieldName(ClassMetadata $meta)
+    public function onFlush(EventArgs $eventArgs)
     {
     {
-        return $meta->getSingleIdentifierFieldName();
+        $ea = $this->getEventAdapter($eventArgs);
+        $om = $ea->getObjectManager();
+        $uow = $om->getUnitOfWork();
+
+        foreach ($ea->getScheduledObjectInsertions($uow) as $object) {
+            $this->createLogEntry(self::ACTION_CREATE, $object, $ea);
+        }
+        foreach ($ea->getScheduledObjectUpdates($uow) as $object) {
+            $this->createLogEntry(self::ACTION_UPDATE, $object, $ea);
+        }
+        foreach ($ea->getScheduledObjectDeletions($uow) as $object) {
+            $this->createLogEntry(self::ACTION_REMOVE, $object, $ea);
+        }
     }
     }
-    
+
     /**
     /**
-     * {@inheritdoc}
+     * {@inheritDoc}
      */
      */
-    protected function getObject(EventArgs $args)
+    protected function getNamespace()
     {
     {
-        return $args->getEntity();
+        return __NAMESPACE__;
     }
     }
-    
+
     /**
     /**
-     * {@inheritdoc}
+     * Create a new Log instance
+     *
+     * @param string $action
+     * @param object $object
+     * @param AdapterInterface $ea
+     * @return void
      */
      */
-    protected function getNewVersion(ClassMetadata $meta, ObjectManager $om, $object)
+    private function createLogEntry($action, $object, AdapterInterface $ea)
     {
     {
-        $objectMeta = $om->getClassMetadata(get_class($object));
-        $identifierField = $this->getSingleIdentifierFieldName($objectMeta);
-        $objectId = $objectMeta->getReflectionProperty($identifierField)->getValue($object);
-        
-        $dql = "SELECT MAX(log.version) FROM {$meta->name} log";
-        $dql .= " WHERE log.objectId = :objectId";
-        $dql .= " AND log.objectClass = :objectClass";
-        
-        $q = $om->createQuery($dql);
-        $q->setParameters(array(
-            'objectId' => $objectId,
-            'objectClass' => $objectMeta->name
-        ));
-        return $q->getSingleScalarResult() + 1;
+        $om = $ea->getObjectManager();
+        $meta = $om->getClassMetadata(get_class($object));
+        if ($config = $this->getConfiguration($om, $meta->name)) {
+            $logEntryClass = $this->getLogEntryClass($ea, $meta->name);
+            $logEntry = new $logEntryClass;
+
+            $logEntry->setAction($action);
+            $logEntry->setUsername($this->username);
+            $logEntry->setObjectClass($meta->name);
+            $logEntry->setLoggedAt();
+
+            // check for the availability of the primary key
+            $identifierField = $ea->getSingleIdentifierFieldName($meta);
+            $objectId = $meta->getReflectionProperty($identifierField)->getValue($object);
+            if (!$objectId && $action === self::ACTION_CREATE) {
+                $this->pendingLogEntryInserts[spl_object_hash($object)] = $logEntry;
+            }
+            $uow = $om->getUnitOfWork();
+            $logEntry->setObjectId($objectId);
+            if ($action !== self::ACTION_REMOVE) {
+                $newValues = array();
+                foreach ($ea->getObjectChangeSet($uow, $object) as $field => $changes) {
+                    $value = $changes[1];
+                    if ($meta->isCollectionValuedAssociation($field)) {
+                        continue;
+                    }
+                    if ($meta->isSingleValuedAssociation($field) && $value) {
+                        $value = $ea->extractIdentifier($om, $value, false);
+                        if (!is_array($value)) {
+                            $this->pendingRelatedObjects[$value] = array(
+                                'log' => $logEntry,
+                                'field' => $field
+                            );
+                        }
+                    }
+                    $newValues[$field] = $value;
+                }
+                $logEntry->setData($newValues);
+            }
+            $version = 1;
+            $logEntryMeta = $om->getClassMetadata($logEntryClass);
+            if ($action !== self::ACTION_CREATE) {
+                $version = $ea->getNewVersion($logEntryMeta, $om, $object);
+            }
+            $logEntry->setVersion($version);
+
+            $om->persist($logEntry);
+            $uow->computeChangeSet($logEntryMeta, $logEntry);
+        }
     }
     }
 }
 }

+ 60 - 0
lib/Gedmo/Loggable/Mapping/Event/Adapter/ODM.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Gedmo\Loggable\Mapping\Event\Adapter;
+
+use Gedmo\Mapping\Event\Adapter\ODM as BaseAdapterODM;
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
+
+/**
+ * Doctrine event adapter for ODM adapted
+ * for Loggable behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo\Loggable\Mapping\Event\Adapter
+ * @subpackage ODM
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class ODM extends BaseAdapterODM
+{
+    /**
+     * Get default LogEntry class used to store the logs
+     *
+     * @return string
+     */
+    public function getDefaultLogEntryClass()
+    {
+        return 'Gedmo\\Loggable\\Document\\LogEntry';
+    }
+
+    /**
+     * Get new version number
+     *
+     * @param ClassMetadataInfo $meta
+     * @param DocumentManager $dm
+     * @param object $object
+     * @return integer
+     */
+    public function getNewVersion(ClassMetadataInfo $meta, DocumentManager $dm, $object)
+    {
+        $objectMeta = $dm->getClassMetadata(get_class($object));
+        $identifierField = $this->getSingleIdentifierFieldName($objectMeta);
+        $objectId = $objectMeta->getReflectionProperty($identifierField)->getValue($object);
+
+        $qb = $dm->createQueryBuilder($meta->name);
+        $qb->select('version');
+        $qb->field('objectId')->equals($objectId);
+        $qb->field('objectClass')->equals($objectMeta->name);
+        $qb->sort('version', 'DESC');
+        $qb->limit(1);
+        $q = $qb->getQuery();
+        $q->setHydrate(false);
+
+        $result = $q->getSingleResult();
+        if ($result) {
+            $result = $result['version'] + 1;
+        }
+        return $result;
+    }
+}

+ 56 - 0
lib/Gedmo/Loggable/Mapping/Event/Adapter/ORM.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Gedmo\Loggable\Mapping\Event\Adapter;
+
+use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM;
+use Doctrine\ORM\EntityManager;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+
+/**
+ * Doctrine event adapter for ORM adapted
+ * for Loggable behavior
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo\Loggable\Mapping\Event\Adapter
+ * @subpackage ORM
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+final class ORM extends BaseAdapterORM
+{
+    /**
+     * Get default LogEntry class used to store the logs
+     *
+     * @return string
+     */
+    public function getDefaultLogEntryClass()
+    {
+        return 'Gedmo\\Loggable\\Entity\\LogEntry';
+    }
+
+    /**
+     * Get new version number
+     *
+     * @param ClassMetadataInfo $meta
+     * @param EntityManager $em
+     * @param object $object
+     * @return integer
+     */
+    public function getNewVersion(ClassMetadataInfo $meta, EntityManager $em, $object)
+    {
+        $objectMeta = $em->getClassMetadata(get_class($object));
+        $identifierField = $this->getSingleIdentifierFieldName($objectMeta);
+        $objectId = $objectMeta->getReflectionProperty($identifierField)->getValue($object);
+
+        $dql = "SELECT MAX(log.version) FROM {$meta->name} log";
+        $dql .= " WHERE log.objectId = :objectId";
+        $dql .= " AND log.objectClass = :objectClass";
+
+        $q = $em->createQuery($dql);
+        $q->setParameters(array(
+            'objectId' => $objectId,
+            'objectClass' => $objectMeta->name
+        ));
+        return $q->getSingleScalarResult() + 1;
+    }
+}