浏览代码

concurent modification support then nested

gediminasm 14 年之前
父节点
当前提交
0204180ff3

+ 4 - 4
README.markdown

@@ -9,7 +9,7 @@ To include the DoctrineExtensions should fire up an autoloader, for example:
 
     $classLoader = new \Doctrine\Common\ClassLoader('DoctrineExtensions', "/path/to/extensions");
     $classLoader->register();
-    
+
 ## Translatable
 
 **Translatable** behavior offers a very handy solution for translating specific record fields
@@ -182,9 +182,9 @@ After these changes translations will be generated, but article in database
 will not change it`s title to "my title in ru". Nevertheless translations in
 ru_ru locale will be available to it.
 
-**Notice:** Using **Translatable** behavior on concurrent updates and inserts flush may
-result in exception. In that case try flushing only inserts and then updates. The test
-case is in unit tests;
+**Translatable** behavior now works on concurent update and insert flush nested
+together with **Sluggable** behavior. That means you can persist whatever you
+want and on flush everything magicaly will be created and updated..
 
 ## Sluggable
 

+ 6 - 3
lib/DoctrineExtensions/Translatable/Exception.php

@@ -31,8 +31,11 @@ class Exception extends \Exception
     
     static public function pendingInserts()
     {
-        return new self("UnitOfWork has pending inserts, cannot request query execution.
-            TranslationListener does not support Concurrent inserts and updates together,
-            on Doctrine 2 Beta4 yet. Try flushing only inserts or updates");
+        return new self("UnitOfWork has pending inserts, cannot request query execution. TranslationListener does not support Concurrent inserts and updates together, on Doctrine 2 Beta4 yet. Try flushing only inserts or updates");
+    }
+    
+    static public function failedToInsert()
+    {
+    	return new self("Failed to insert new Translation record");
     }
 }

+ 49 - 22
lib/DoctrineExtensions/Translatable/TranslationListener.php

@@ -142,14 +142,6 @@ class TranslationListener implements EventSubscriber
                 $this->_handleTranslatableEntityUpdate($em, $entity, false);
             }
         }
-        
-        // all translations which should have been inserted are processed now
-        // this prevents new pending insertions during sheduled updates process
-        $translationMetadata = $em->getClassMetadata(self::TRANSLATION_ENTITY_CLASS);
-        foreach ($this->_pendingTranslationUpdates as $translation) {
-        	$em->persist($translation);
-            $uow->computeChangeSet($translationMetadata, $translation);
-        }
     }
     
     /**
@@ -163,6 +155,7 @@ class TranslationListener implements EventSubscriber
     {
         $em = $args->getEntityManager();
         $entity = $args->getEntity();
+        $uow = $em->getUnitOfWork();
         // check if entity is Translatable and without foreign key
         if ($entity instanceof Translatable && count($this->_pendingTranslationInserts)) {
         	$oid = spl_object_hash($entity);
@@ -170,14 +163,32 @@ class TranslationListener implements EventSubscriber
                 // load the pending translations without key
         		$translations = $this->_pendingTranslationInserts[$oid];
         		foreach ($translations as $translation) {
-	                // schedule an extra update for the foreign key
-	                $uow = $em->getUnitOfWork();
-	                $uow->scheduleExtraUpdate($translation, array(
-	                    'foreignKey' => array(null, $entity->getId())
-	                ));
+	                $translation->setForeignKey($entity->getId());
+	                $this->_insertTranslationRecord($em, $translation);
         		}
             }
         }
+        // all translations which should have been inserted are processed now
+        // this prevents new pending insertions during sheduled updates process
+        // and twice queries
+        if (!$uow->hasPendingInsertions() && count($this->_pendingTranslationUpdates)) {
+        	foreach ($this->_pendingTranslationUpdates as $candidate) {
+        		$translation = $this->_findTranslation(
+                    $em,
+                    $candidate->getForeignKey(),
+                    $candidate->getEntity(),
+                    $candidate->getLocale(),
+                    $candidate->getField()
+                );
+                if (!$translation) {
+                	$this->_insertTranslationRecord($em, $candidate);
+                } else {
+                	$uow->scheduleExtraUpdate($translation, array(
+                        'content' => array(null, $candidate->getContent())
+                    ));
+                }
+        	}
+        }
     }
     
     /**
@@ -256,8 +267,9 @@ class TranslationListener implements EventSubscriber
         $translatableFields = $entity->getTranslatableFields();
         foreach ($translatableFields as $field) {
         	$translation = null;
+        	$scheduleUpdate = false;
         	// check if translation allready is created
-        	if (!$isInsert) {
+        	if (!$isInsert && !$uow->hasPendingInsertions()) {
                 $translation = $this->_findTranslation(
                     $em,
                     $entityId,
@@ -267,7 +279,6 @@ class TranslationListener implements EventSubscriber
                 );
         	}
             // create new translation
-            $scheduleUpdate = false;
             if (!$translation) {
                 $translation = new Translation;
                 $translation->setLocale($locale);
@@ -280,20 +291,18 @@ class TranslationListener implements EventSubscriber
             // set the translated field, take value using getter
             $getter = 'get' . ucfirst($field);
             $translation->setContent($entity->{$getter}());
-            
-            if ($scheduleUpdate) {
+            if ($scheduleUpdate && $uow->hasPendingInsertions()) {
                 // need to shedule new Translation insert to avoid query on pending insert
                 $this->_pendingTranslationUpdates[] = $translation;
+            } elseif ($isInsert && is_null($entityId)) {
+                // if we do not have the primary key yet available
+                // keep this translation in memory to insert it later with foreign key
+                $this->_pendingTranslationInserts[spl_object_hash($entity)][$field] = $translation;
             } else {
             	// persist and compute change set for translation
                 $em->persist($translation);
                 $uow->computeChangeSet($translationMetadata, $translation);
             }
-            // if we do not have the primary key yet available
-            // keep this translation in memory for later update
-            if ($isInsert && is_null($entityId)) {
-            	$this->_pendingTranslationInserts[spl_object_hash($entity)][$field] = $translation;
-            }
         }
         // check if we have default translation and need to reset the translation
         if (!$isInsert && strlen($this->_defaultLocale)) {
@@ -374,4 +383,22 @@ class TranslationListener implements EventSubscriber
     		throw Exception::undefinedLocale();
     	}
     }
+    
+    private function _insertTranslationRecord(EntityManager $em, $translation)
+    {
+        $translationMetadata = $em->getClassMetadata(self::TRANSLATION_ENTITY_CLASS);
+        
+        $data = array(
+            'locale' => $translation->getLocale(),
+            'foreign_key' => $translation->getForeignKey(),
+            'entity' => $translation->getEntity(),
+            'field' => $translation->getField(),
+            'content' => $translation->getContent()
+        );
+        
+        $table = $translationMetadata->getTableName();
+        if (!$em->getConnection()->insert($table, $data)) {
+            throw Exception::failedToInsert();
+        }
+    }
 }

+ 15 - 9
tests/bootstrap.php

@@ -13,21 +13,27 @@
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
 
-if (!defined('DOCTRINE_LIBRARY_PATH') || !strlen(DOCTRINE_LIBRARY_PATH)) {
-	die('path to doctrine library must be defined in phpunit.xml configuration');
+if (!defined('DOCTRINE_LIBRARY_PATH')) {
+    die('path to doctrine library must be defined in phpunit.xml configuration');
 }
 
-set_include_path(implode(PATH_SEPARATOR, array(
-    realpath(DOCTRINE_LIBRARY_PATH),
-    get_include_path(),
-)));
+// if empty string given, assume its in include path allready
+if (strlen(DOCTRINE_LIBRARY_PATH)) {
+    set_include_path(implode(PATH_SEPARATOR, array(
+        realpath(DOCTRINE_LIBRARY_PATH),
+        get_include_path(),
+    )));
+}
 
 !defined('DS') && define('DS', DIRECTORY_SEPARATOR);
 !defined('TESTS_PATH') && define('TESTS_PATH', __DIR__);
 
-$classLoaderFile = DOCTRINE_LIBRARY_PATH . DS . 'Doctrine/Common/ClassLoader.php';
-if (!file_exists($classLoaderFile)) {
-	die('cannot find doctrine classloader, check the library path');
+$classLoaderFile = 'Doctrine/Common/ClassLoader.php';
+if (strlen(DOCTRINE_LIBRARY_PATH)) {
+    $classLoaderFile = DOCTRINE_LIBRARY_PATH . DS . $classLoaderFile;
+    if (!file_exists($classLoaderFile)) {
+        die('cannot find doctrine classloader, check the library path');
+    }
 }
 require_once $classLoaderFile;
 $classLoader = new Doctrine\Common\ClassLoader('Doctrine');