Ver código fonte

[timestampable] odm listener implementation

gediminasm 14 anos atrás
pai
commit
887e37d547

+ 1 - 0
README.markdown

@@ -20,6 +20,7 @@ half of extensions can be used with ODM also.
 
 - Translatable
 - Sluggable
+- Timestampable
 
 Are allready ported to support ODM MongoDB
 

+ 187 - 0
lib/Gedmo/Timestampable/AbstractTimestampableListener.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace Gedmo\Timestampable;
+
+use Doctrine\Common\EventArgs,
+    Gedmo\Mapping\MappedEventSubscriber;
+
+/**
+ * The AbstractTimestampableListener is an abstract class
+ * of timestampable listener in order to support diferent
+ * object managers.
+ * 
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Timestampable
+ * @subpackage AbstractTimestampableListener
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+abstract class AbstractTimestampableListener extends MappedEventSubscriber
+{
+    /**
+     * Mapps additional metadata for the Entity
+     * 
+     * @param EventArgs $eventArgs
+     * @return void
+     */
+    public function loadClassMetadata(EventArgs $eventArgs)
+    {
+        $this->loadMetadataForObjectClass($this->getObjectManager($eventArgs), $eventArgs->getClassMetadata());
+    }
+    
+    /**
+     * Looks for Timestampable objects being updated
+     * to update modification date
+     * 
+     * @param EventArgs $args
+     * @return void
+     */
+    public function onFlush(EventArgs $args)
+    {
+        $om = $this->getObjectManager($args);
+        $uow = $om->getUnitOfWork();
+        // check all scheduled updates
+        foreach ($this->getScheduledObjectUpdates($uow) as $object) {
+            $objectClass = get_class($object);
+            if ($config = $this->getConfiguration($om, $objectClass)) {
+                $meta = $om->getClassMetadata($objectClass);
+                $needChanges = false;
+
+                if (isset($config['update'])) {
+                    $needChanges = true;
+                    foreach ($config['update'] as $field) {
+                        $meta->getReflectionProperty($field)
+                            ->setValue($object, $this->getDateValue($meta, $field));
+                    }
+                }
+                
+                if (isset($config['change'])) {
+                    $changeSet = $this->getObjectChangeSet($uow, $object);
+                    foreach ($config['change'] as $options) {
+                        $tracked = $options['trackedField'];
+                        $trackedChild = null;
+                        $parts = explode('.', $tracked);
+                        if (isset($parts[1])) {
+                            $tracked = $parts[0];
+                            $trackedChild = $parts[1];
+                        }
+                        
+                        if (isset($changeSet[$tracked])) {
+                            $changes = $changeSet[$tracked];
+                            if (isset($trackedChild)) {
+                                $changingObject = $changes[1];
+                                if (!is_object($changingObject)) {
+                                    throw new \Gedmo\Exception\UnexpectedValueException("Field - [{$field}] is expected to be object in class - {$meta->name}");
+                                }
+                                $objectMeta = $om->getClassMetadata(get_class($changingObject));
+                                $value = $objectMeta->getReflectionProperty($trackedChild)
+                                    ->getValue($changingObject);
+                            } else {
+                                $value = $changes[1];
+                            }
+                            
+                            if ($options['value'] == $value) {
+                                $needChanges = true;
+                                $meta->getReflectionProperty($options['field'])
+                                    ->setValue($object, $this->getDateValue($meta, $options['field']));
+                            }
+                        }
+                    }
+                }
+                
+                if ($needChanges) {
+                    $this->recomputeSingleObjectChangeSet($uow, $meta, $object);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Checks for persisted Timestampable objects
+     * to update creation and modification dates
+     * 
+     * @param EventArgs $args
+     * @return void
+     */
+    public function prePersist(EventArgs $args)
+    {
+        $om = $this->getObjectManager($args);
+        $object = $this->getObject($args);
+        
+        $meta = $om->getClassMetadata(get_class($object));
+        if ($config = $this->getConfiguration($om, $meta->name)) {
+            if (isset($config['update'])) {
+                foreach ($config['update'] as $field) {
+                    $meta->getReflectionProperty($field)
+                        ->setValue($object, $this->getDateValue($meta, $field));
+                }
+            }
+            
+            if (isset($config['create'])) {
+                foreach ($config['create'] as $field) {
+                    $meta->getReflectionProperty($field)
+                        ->setValue($object, $this->getDateValue($meta, $field));
+                }
+            }
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    protected function _getNamespace()
+    {
+        return __NAMESPACE__;
+    }
+    
+    /**
+     * Get the ObjectManager from EventArgs
+     *
+     * @param EventArgs $args
+     * @return object
+     */
+    abstract protected function getObjectManager(EventArgs $args);
+    
+    /**
+     * Get the Object from EventArgs
+     *
+     * @param EventArgs $args
+     * @return object
+     */
+    abstract protected function getObject(EventArgs $args);
+    
+    /**
+     * Get the scheduled object updates from a UnitOfWork
+     *
+     * @param UnitOfWork $uow
+     * @return array
+     */
+    abstract protected function getScheduledObjectUpdates($uow);
+    
+    /**
+     * Get the object changeset from a UnitOfWork
+     *
+     * @param UnitOfWork $uow
+     * @param Object $object
+     * @return array
+     */
+    abstract protected function getObjectChangeSet($uow, $object);
+    
+    /**
+     * Recompute the single object changeset from a UnitOfWork
+     *
+     * @param UnitOfWork $uow
+     * @param Object $object
+     * @return void
+     */
+    abstract protected function recomputeSingleObjectChangeSet($uow, $meta, $object);
+    
+    /**
+     * Get the date value
+     * 
+     * @param ClassMetadata $meta
+     * @param string $field
+     * @return mixed
+     */
+    abstract protected function getDateValue($meta, $field);
+}

+ 2 - 1
lib/Gedmo/Timestampable/Mapping/Driver/Annotation.php

@@ -33,7 +33,8 @@ class Annotation implements Driver
     private $_validTypes = array(
         'date',
         'time',
-        'datetime'
+        'datetime',
+        'timestamp'
     );
     
     /**

+ 2 - 1
lib/Gedmo/Timestampable/Mapping/Driver/Yaml.php

@@ -34,7 +34,8 @@ class Yaml extends File implements Driver
     private $_validTypes = array(
         'date',
         'time',
-        'datetime'
+        'datetime',
+        'timestamp'
     );
     
     /**

+ 86 - 0
lib/Gedmo/Timestampable/ODM/MongoDB/TimestampableListener.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Gedmo\Timestampable\ODM\MongoDB;
+
+use Doctrine\ODM\MongoDB\Events,
+    Doctrine\Common\EventArgs,
+    Gedmo\Timestampable\AbstractTimestampableListener;
+
+/**
+ * The Timestampable listener handles the update of
+ * dates on creation and update of entity.
+ * 
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Timestampable.ODM.MongoDB
+ * @subpackage TimestampableListener
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class TimestampableListener extends AbstractTimestampableListener
+{    
+    /**
+     * Specifies the list of events to listen
+     * 
+     * @return array
+     */
+    public function getSubscribedEvents()
+    {
+        return array(
+            Events::prePersist,
+            Events::onFlush,
+            Events::loadClassMetadata
+        );
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getObjectManager(EventArgs $args)
+    {
+        return $args->getDocumentManager();
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getObject(EventArgs $args)
+    {
+        return $args->getDocument();
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getObjectChangeSet($uow, $object)
+    {
+        return $uow->getDocumentChangeSet($object);
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getScheduledObjectUpdates($uow)
+    {
+        return $uow->getScheduledDocumentUpdates();
+    }
+    
+	/**
+     * {@inheritdoc}
+     */
+    protected function recomputeSingleObjectChangeSet($uow, $meta, $object)
+    {
+        $uow->recomputeSingleDocumentChangeSet($meta, $object);
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getDateValue($meta, $field)
+    {
+        $mapping = $meta->getFieldMapping($field);
+        if (isset($mapping['type']) && $mapping['type'] === 'timestamp') {
+            return time();
+        }
+        return new \DateTime();
+    }
+}

+ 33 - 104
lib/Gedmo/Timestampable/TimestampableListener.php

@@ -3,11 +3,7 @@
 namespace Gedmo\Timestampable;
 
 use Doctrine\ORM\Events,
-    Doctrine\ORM\Event\LifecycleEventArgs,
-    Doctrine\ORM\Event\OnFlushEventArgs,
-    Doctrine\ORM\Event\LoadClassMetadataEventArgs,
-    Gedmo\Mapping\MappedEventSubscriber,
-    Doctrine\ORM\EntityManager;
+    Doctrine\Common\EventArgs;
 
 /**
  * The Timestampable listener handles the update of
@@ -19,7 +15,7 @@ use Doctrine\ORM\Events,
  * @link http://www.gediminasm.org
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
-class TimestampableListener extends MappedEventSubscriber
+class TimestampableListener extends AbstractTimestampableListener
 {
     /**
      * Specifies the list of events to listen
@@ -35,118 +31,51 @@ class TimestampableListener extends MappedEventSubscriber
         );
     }
     
-    /**
-     * Mapps additional metadata for the Entity
-     * 
-     * @param LoadClassMetadataEventArgs $eventArgs
-     * @return void
+	/**
+     * {@inheritdoc}
      */
-    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
+    protected function getObjectManager(EventArgs $args)
     {
-        $this->loadMetadataForObjectClass($eventArgs->getEntityManager(), $eventArgs->getClassMetadata());
+        return $args->getEntityManager();
     }
     
     /**
-     * Looks for Timestampable entities being updated
-     * to update modification date
-     * 
-     * @param OnFlushEventArgs $args
-     * @return void
+     * {@inheritdoc}
      */
-    public function onFlush(OnFlushEventArgs $args)
+    protected function getObject(EventArgs $args)
     {
-        $em = $args->getEntityManager();
-        $uow = $em->getUnitOfWork();
-        // check all scheduled updates
-        foreach ($uow->getScheduledEntityUpdates() as $entity) {
-            $entityClass = get_class($entity);
-            if ($config = $this->getConfiguration($em, $entityClass)) {
-                $meta = $em->getClassMetadata($entityClass);
-                $needChanges = false;
-                
-                if (isset($config['update'])) {
-                    $needChanges = true;
-                    foreach ($config['update'] as $field) {
-                        $meta->getReflectionProperty($field)
-                            ->setValue($entity, new \DateTime('now'));
-                    }
-                }
-                
-                if (isset($config['change'])) {
-                    $changeSet = $uow->getEntityChangeSet($entity);
-                    foreach ($config['change'] as $options) {
-                        $tracked = $options['trackedField'];
-                        $trackedChild = null;
-                        $parts = explode('.', $tracked);
-                        if (isset($parts[1])) {
-                            $tracked = $parts[0];
-                            $trackedChild = $parts[1];
-                        }
-                        
-                        if (isset($changeSet[$tracked])) {
-                            $changes = $changeSet[$tracked];
-                            if (isset($trackedChild)) {
-                                $object = $changes[1];
-                                if (!is_object($object)) {
-                                    throw new \Gedmo\Exception\UnexpectedValueException("Field - [{$field}] is expected to be object in class - {$meta->name}");
-                                }
-                                $objectMeta = $em->getClassMetadata(get_class($object));
-                                $value = $objectMeta->getReflectionProperty($trackedChild)
-                                    ->getValue($object);
-                            } else {
-                                $value = $changes[1];
-                            }
-                            if ($options['value'] == $value) {
-                                $needChanges = true;
-                                $meta->getReflectionProperty($options['field'])
-                                    ->setValue($entity, new \DateTime('now'));
-                            }
-                        }
-                    }
-                }
-                
-                if ($needChanges) {
-                    $uow->recomputeSingleEntityChangeSet($meta, $entity);
-                }
-            }
-        }
+        return $args->getEntity();
     }
     
     /**
-     * Checks for persisted Timestampable entities
-     * to update creation and modification dates
-     * 
-     * @param LifecycleEventArgs $args
-     * @return void
+     * {@inheritdoc}
      */
-    public function prePersist(LifecycleEventArgs $args)
+    protected function getObjectChangeSet($uow, $object)
     {
-        $em = $args->getEntityManager();
-        $entity = $args->getEntity();
-        
-        $meta = $em->getClassMetadata(get_class($entity));
-        if ($config = $this->getConfiguration($em, $meta->name)) {
-            if (isset($config['update'])) {
-                foreach ($config['update'] as $field) {
-                    $meta->getReflectionProperty($field)
-                        ->setValue($entity, new \DateTime('now'));
-                }
-            }
-            
-            if (isset($config['create'])) {
-                foreach ($config['create'] as $field) {
-                    $meta->getReflectionProperty($field)
-                        ->setValue($entity, new \DateTime('now'));
-                }
-            }
-        }
+        return $uow->getEntityChangeSet($object);
     }
     
-    /**
-     * {@inheritDoc}
+	/**
+     * {@inheritdoc}
+     */
+    protected function getScheduledObjectUpdates($uow)
+    {
+        return $uow->getScheduledEntityUpdates();
+    }
+    
+	/**
+     * {@inheritdoc}
+     */
+    public function recomputeSingleObjectChangeSet($uow, $meta, $object)
+    {
+        $uow->recomputeSingleEntityChangeSet($meta, $object);
+    }
+    
+	/**
+     * {@inheritdoc}
      */
-    protected function _getNamespace()
+    protected function getDateValue($meta, $field)
     {
-        return __NAMESPACE__;
+        return new \DateTime();
     }
-}
+}

+ 86 - 0
tests/Gedmo/Timestampable/Fixture/Document/Article.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Timestampable\Fixture\Document;
+
+/** 
+ * @Document(collection="articles")
+ */
+class Article
+{
+    /** @Id */
+    private $id;
+
+    /**
+     * @String
+     */
+    private $title;
+    
+    /** 
+     * @ReferenceOne(targetDocument="Type")
+     */
+    private $type;
+    
+    /**
+     * @var timestamp $created
+     * 
+     * @Timestamp
+     * @gedmo:Timestampable(on="create")
+     */
+    private $created;
+    
+    /**
+     * @var date $updated
+     *
+     * @Date
+     * @gedmo:Timestampable
+     */
+    private $updated;
+    
+    /**
+     * @var date $published
+     *
+     * @Date
+     * @gedmo:Timestampable(on="change", field="type.title", value="Published")
+     */
+    private $published;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+    
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+    
+    public function getCreated()
+    {
+        return $this->created;
+    }
+    
+    public function getPublished()
+    {
+        return $this->published;
+    }
+    
+    public function getUpdated()
+    {
+        return $this->updated;
+    }
+    
+    public function setType(Type $type)
+    {
+        $this->type = $type;
+    }
+    
+    public function getType()
+    {
+        return $this->type;
+    }
+}

+ 47 - 0
tests/Gedmo/Timestampable/Fixture/Document/Type.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Timestampable\Fixture\Document;
+
+/** 
+ * @Document(collection="types")
+ */
+class Type
+{
+    /** @Id */
+    private $id;
+
+    /**
+     * @String
+     */
+    private $title;
+    
+    /**
+     * @String
+     */
+    private $identifier;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+    
+    public function setTitle($title)
+    {
+        $this->title = $title;
+    }
+
+    public function getTitle()
+    {
+        return $this->title;
+    }
+    
+    public function getIdentifier()
+    {
+        return $this->identifier;
+    }
+    
+    public function setIdentifier($identifier)
+    {
+        $this->identifier = $identifier;
+    }
+}

+ 119 - 0
tests/Gedmo/Timestampable/TimestampableDocumentTest.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace Gedmo\Timestampable;
+
+use Timestampable\Fixture\Document\Article,
+    Timestampable\Fixture\Document\Type;
+
+/**
+ * These are tests for Timestampable behavior ODM implementation
+ * 
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Timestampable
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class TimestampableDocumentTest extends \PHPUnit_Framework_TestCase
+{
+    const TEST_CLASS_ARTICLE = 'Timestampable\Fixture\Document\Article';
+    const TEST_CLASS_TYPE = 'Timestampable\Fixture\Document\Type';
+    
+    /**
+     * @var DocumentManager
+     */
+    private $dm;
+    
+    public function setUp()
+    {
+        $config = new \Doctrine\ODM\MongoDB\Configuration();
+        $config->setProxyDir(__DIR__ . '/Proxy');
+        $config->setProxyNamespace('Gedmo\Timestampable\Proxy');
+        $config->setHydratorDir(__DIR__ . '/Hydrator');
+        $config->setHydratorNamespace('Hydrator');
+        $config->setDefaultDB('gedmo_timestampable_tests');
+        
+        
+        $config->setLoggerCallable(function(array $log) {
+            print_r($log);
+        });
+        
+        
+        $reader = new \Doctrine\Common\Annotations\AnnotationReader();
+        $reader->setDefaultAnnotationNamespace('Doctrine\ODM\MongoDB\Mapping\\');
+        $config->setMetadataDriverImpl(
+            new \Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver(
+                $reader, 
+                array(__DIR__ . '/Fixture/Document')
+            )
+        );
+        
+        $evm = new \Doctrine\Common\EventManager();
+        $timestampableListener = new \Gedmo\Timestampable\ODM\MongoDB\TimestampableListener();
+        $evm->addEventSubscriber($timestampableListener);
+        
+        if (!extension_loaded('mongo')) {
+            $this->markTestSkipped('Mongo extension is not loaded in PHP.');
+        }
+        
+        try {
+            $this->dm = \Doctrine\ODM\MongoDB\DocumentManager::create(
+                new \Doctrine\MongoDB\Connection(),
+                $config, 
+                $evm
+            );
+        
+            $this->populate();
+        } catch (\MongoException $e) {
+            $this->markTestSkipped('Doctrine MongoDB ODM connection problem.');
+        }
+    }
+    
+    public function testTimestampable()
+    {
+        $repo = $this->dm->getRepository(self::TEST_CLASS_ARTICLE);
+        $article = $repo->findOneByTitle('Timestampable Article');
+        
+        $date = new \DateTime();
+        $this->assertEquals(
+            time(), 
+            (string)$article->getCreated()
+        );
+        $this->assertEquals(
+            $date->format('Y-m-d H:i:s'), 
+            $article->getUpdated()->format('Y-m-d H:i:s')
+        );
+        
+        $published = new Type;
+        $published->setIdentifier('published');
+        $published->setTitle('Published');
+        
+        $article->setType($published);
+        $this->dm->persist($article);
+        $this->dm->persist($published);
+        $this->dm->flush();
+        $this->dm->clear();
+        
+        $article = $repo->findOneByTitle('Timestampable Article');
+        $date = new \DateTime();
+        $this->assertEquals(
+            $date->format('Y-m-d H:i:s'), 
+            $article->getPublished()->format('Y-m-d H:i:s')
+        );
+    }
+    
+    private function populate()
+    {
+        $qb = $this->dm->createQueryBuilder(self::TEST_CLASS_ARTICLE);
+        $qb->remove()->getQuery()->execute();
+        
+        $qb = $this->dm->createQueryBuilder(self::TEST_CLASS_TYPE);
+        $qb->remove()->getQuery()->execute();
+        
+        $art0 = new Article();
+        $art0->setTitle('Timestampable Article');
+        
+        $this->dm->persist($art0);
+        $this->dm->flush();
+        $this->dm->clear();
+    }
+}