浏览代码

timestampable with annotation support approach

gediminasm 14 年之前
父节点
当前提交
25f941fafa

+ 20 - 0
lib/DoctrineExtensions/Timestampable/Exception.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace DoctrineExtensions\Timestampable;
+
+/**
+ * The exception list for Timestampable behavior
+ * 
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package DoctrineExtensions.Timestampable
+ * @subpackage Exception
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class Exception extends \Exception
+{    
+    static public function notValidFieldType($field, $class)
+    {
+        return new self("Timestampable field - [{$field}] type is not valid date or time field in class - {$class}");
+    }
+}

+ 12 - 0
lib/DoctrineExtensions/Timestampable/Mapping/Annotations.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace DoctrineExtensions\Timestampable\Mapping;
+
+use Doctrine\Common\Annotations\Annotation;
+
+final class OnCreate extends Annotation {}
+final class OnUpdate extends Annotation {}
+final class OnChange extends Annotation {
+    public $field;
+    public $value;
+}

+ 12 - 1
lib/DoctrineExtensions/Timestampable/Timestampable.php

@@ -14,5 +14,16 @@ namespace DoctrineExtensions\Timestampable;
  */
 interface Timestampable
 {
-    // timestampable expects, fields: modified, created or updated
+    // timestampable expects annotations on properties
+    
+    // Timestampable:OnCreate - dates which should be updated on creation
+    // Timestampable:OnUpdate - dates which should be updated on update
+    
+    /**
+     * example
+     * 
+     * @Timestampable:OnCreate
+     * @Column(type="date")
+     */
+    //$created
 }

+ 79 - 23
lib/DoctrineExtensions/Timestampable/TimestampableListener.php

@@ -6,7 +6,10 @@ use Doctrine\Common\EventSubscriber,
     Doctrine\ORM\Events,
     Doctrine\ORM\Event\LifecycleEventArgs,
     Doctrine\ORM\Event\OnFlushEventArgs,
-    Doctrine\ORM\EntityManager;
+    Doctrine\ORM\Event\LoadClassMetadataEventArgs,
+    Doctrine\ORM\EntityManager,
+    Doctrine\ORM\Mapping\ClassMetadata,
+    Doctrine\Common\Annotations\AnnotationReader;
 
 /**
  * The Timestampable listener handles the update of
@@ -20,6 +23,14 @@ use Doctrine\Common\EventSubscriber,
  */
 class TimestampableListener implements EventSubscriber
 {   
+    /**
+     * List of metadata configurations for Timestampable
+     * classes, from annotations
+     * 
+     * @var array
+     */
+    protected $_configurations = array();
+    
     /**
      * List of types which are valid for timestamp
      * 
@@ -40,10 +51,52 @@ class TimestampableListener implements EventSubscriber
     {
         return array(
             Events::prePersist,
-            Events::onFlush
+            Events::onFlush,
+            Events::loadClassMetadata
         );
     }
     
+    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
+    {
+        $meta = $eventArgs->getClassMetadata();
+        $class = $meta->getReflectionClass();
+        if ($class->implementsInterface('DoctrineExtensions\Timestampable\Timestampable')) {
+            require_once __DIR__ . '/Mapping/Annotations.php';
+            $reader = new AnnotationReader();
+            $reader->setAnnotationNamespaceAlias(
+                'DoctrineExtensions\Timestampable\Mapping\\', 'Timestampable'
+            );
+    
+            // property annotations
+            foreach ($class->getProperties() as $property) {
+                // on create
+                $onCreate = $reader->getPropertyAnnotation(
+                    $property, 
+                    'DoctrineExtensions\Timestampable\Mapping\OnCreate'
+                );
+                if ($onCreate) {
+                    $field = $property->getName();
+                    if (!$this->_isValidField($meta, $field)) {
+                        throw Exception::notValidFieldType($field, $meta->name);
+                    }
+                    $this->_configurations[$meta->name]['onCreate'][] = $field;
+                }
+                // on update
+                $onUpdate = $reader->getPropertyAnnotation(
+                    $property, 
+                    'DoctrineExtensions\Timestampable\Mapping\OnUpdate'
+                );
+                if ($onUpdate) {
+                    $field = $property->getName();
+                    if (!$this->_isValidField($meta, $field)) {
+                        throw Exception::notValidFieldType($field, $meta->name);
+                    }
+                    $this->_configurations[$meta->name]['onUpdate'][] = $field;
+                }
+            }
+        }
+    }
+    
     /**
      * Looks for Timestampable entities being updated
      * to update modification date
@@ -58,15 +111,16 @@ class TimestampableListener implements EventSubscriber
         // check all scheduled updates
         foreach ($uow->getScheduledEntityUpdates() as $entity) {
             if ($entity instanceof Timestampable) {
-                $meta = $em->getClassMetadata(get_class($entity));
+                $entityClass = get_class($entity);
+                $meta = $em->getClassMetadata($entityClass);
                 $needChanges = false;
                 
-                if ($this->_isFieldAvailable($em, $entity, 'updated')) {
-                    $needChanges = true;
-                    $meta->getReflectionProperty('updated')->setValue($entity, new \DateTime('now'));
-                } elseif ($this->_isFieldAvailable($em, $entity, 'modified')) {
+                if (isset($this->_configurations[$entityClass]['onUpdate'])) {
                     $needChanges = true;
-                    $meta->getReflectionProperty('modified')->setValue($entity, new \DateTime('now'));
+                    foreach ($this->_configurations[$entityClass]['onUpdate'] as $field) {
+                        $meta->getReflectionProperty($field)
+                            ->setValue($entity, new \DateTime('now'));
+                    }
                 }
                 
                 if ($needChanges) {
@@ -90,14 +144,21 @@ class TimestampableListener implements EventSubscriber
         $uow = $em->getUnitOfWork();
         
         if ($entity instanceof Timestampable) {
-            $meta = $em->getClassMetadata(get_class($entity));
-            if ($this->_isFieldAvailable($em, $entity, 'created')) {
-                $meta->getReflectionProperty('created')->setValue($entity, new \DateTime('now'));
+            $entityClass = get_class($entity);
+            $meta = $em->getClassMetadata($entityClass);
+                
+            if (isset($this->_configurations[$entityClass]['onUpdate'])) {
+                foreach ($this->_configurations[$entityClass]['onUpdate'] as $field) {
+                    $meta->getReflectionProperty($field)
+                        ->setValue($entity, new \DateTime('now'));
+                }
             }
-            if ($this->_isFieldAvailable($em, $entity, 'updated')) {
-                $meta->getReflectionProperty('updated')->setValue($entity, new \DateTime('now'));
-            } elseif ($this->_isFieldAvailable($em, $entity, 'modified')) {
-                $meta->getReflectionProperty('modified')->setValue($entity, new \DateTime('now'));
+            
+            if (isset($this->_configurations[$entityClass]['onCreate'])) {
+                foreach ($this->_configurations[$entityClass]['onCreate'] as $field) {
+                    $meta->getReflectionProperty($field)
+                        ->setValue($entity, new \DateTime('now'));
+                }
             }
         }
     }
@@ -106,17 +167,12 @@ class TimestampableListener implements EventSubscriber
      * Checks if $field exists on entity and
      * if it is in right type
      * 
-     * @param EntityManager $em
-     * @param Timestampable $entity
+     * @param ClassMetadata $meta
      * @param string $field
      * @return boolean
      */
-    protected function _isFieldAvailable(EntityManager $em, Timestampable $entity, $field)
+    protected function _isValidField(ClassMetadata $meta, $field)
     {
-        $meta = $em->getClassMetadata(get_class($entity));
-        if ($meta->hasField($field) && in_array($meta->getTypeOfField($field), $this->_validTypes)) {
-            return true;
-        }
-        return false;
+        return in_array($meta->getTypeOfField($field), $this->_validTypes);
     }
 }

+ 18 - 18
lib/DoctrineExtensions/Translatable/Repository/TranslationRepository.php

@@ -69,24 +69,24 @@ class TranslationRepository extends EntityRepository
      */
     public function findEntityByTranslatedField($field, $value, $class)
     {
-    	$entity = null;
-    	$meta = $this->_em->getClassMetadata($class);
-    	if ($meta->hasField($field)) {
-    		$dql = "SELECT trans.foreignKey FROM {$this->_entityName} trans";
-    		$dql .= ' WHERE trans.entity = :class';
-    		$dql .= ' AND trans.field = :field';
-    		$dql .= ' AND trans.content = :value';
-    		$q = $this->_em->createQuery($dql);
-        	$q->setParameters(compact('class', 'field', 'value'));
-    		$q->setMaxResults(1);
-    		$result = $q->getArrayResult();
-    		$id = count($result) ? $result[0]['foreignKey'] : null;
-    			
-    		if ($id) {
-    			$entity = $this->_em->find($class, $id);
-    		}
-    	}
-    	return $entity;
+        $entity = null;
+        $meta = $this->_em->getClassMetadata($class);
+        if ($meta->hasField($field)) {
+            $dql = "SELECT trans.foreignKey FROM {$this->_entityName} trans";
+            $dql .= ' WHERE trans.entity = :class';
+            $dql .= ' AND trans.field = :field';
+            $dql .= ' AND trans.content = :value';
+            $q = $this->_em->createQuery($dql);
+            $q->setParameters(compact('class', 'field', 'value'));
+            $q->setMaxResults(1);
+            $result = $q->getArrayResult();
+            $id = count($result) ? $result[0]['foreignKey'] : null;
+                
+            if ($id) {
+                $entity = $this->_em->find($class, $id);
+            }
+        }
+        return $entity;
     }
     
     /**

+ 3 - 1
tests/DoctrineExtensions/Timestampable/Fixture/Article.php

@@ -23,7 +23,8 @@ class Article implements Timestampable
     
     /**
      * @var datetime $created
-     *
+     * 
+     * @Timestampable:OnCreate
      * @Column(name="created", type="date")
      */
     private $created;
@@ -32,6 +33,7 @@ class Article implements Timestampable
      * @var datetime $updated
      *
      * @Column(name="updated", type="datetime")
+     * @Timestampable:OnUpdate
      */
     private $updated;
 

+ 1 - 0
tests/DoctrineExtensions/Timestampable/Fixture/Comment.php

@@ -26,6 +26,7 @@ class Comment implements Timestampable
      * @var datetime $modified
      *
      * @Column(name="modified", type="time")
+     * @Timestampable:OnUpdate
      */
     private $modified;
 

+ 6 - 6
tests/DoctrineExtensions/Translatable/TranslatableIdentifierTest.php

@@ -88,18 +88,18 @@ class TranslatableIdentifierTest extends \PHPUnit_Framework_TestCase
         
         // test the entity load by translated title
         $object = $repo->findEntityByTranslatedField(
-        	'title',
-        	'title in en',
-        	self::TEST_ENTITY_CLASS
+            'title',
+            'title in en',
+            self::TEST_ENTITY_CLASS
         );
         
         $this->assertTrue($object instanceof Translatable);
         $this->assertEquals($this->testObjectId, $object->getUid());
         
         $object = $repo->findEntityByTranslatedField(
-        	'title',
-        	'title in de',
-        	self::TEST_ENTITY_CLASS
+            'title',
+            'title in de',
+            self::TEST_ENTITY_CLASS
         );
         
         $this->assertEquals($this->testObjectId, $object->getUid());

+ 1 - 1
tests/DoctrineExtensions/Tree/temp/TreeFixtureCategoryProxy.php

@@ -66,6 +66,6 @@ class TreeFixtureCategoryProxy extends \Tree\Fixture\Category implements \Doctri
 
     public function __sleep()
     {
-        return array('__isInitialized__', 'id', 'title', 'lft', 'rgt', 'parent', 'children', 'comments');
+        return array('__isInitialized__', 'id', 'title', 'rgt', 'parent', 'children', 'comments', 'lft');
     }
 }