[+TASK[+BUGFIX] Extbase (Persistence): Lazy loading of objects no longer triggers...
authorJochen Rau <j.rau@web.de>
Sun, 20 Jun 2010 07:18:23 +0000 (07:18 +0000)
committerJochen Rau <j.rau@web.de>
Sun, 20 Jun 2010 07:18:23 +0000 (07:18 +0000)
typo3/sysext/extbase/Classes/DomainObject/AbstractDomainObject.php
typo3/sysext/extbase/Classes/DomainObject/AbstractEntity.php
typo3/sysext/extbase/Classes/DomainObject/DomainObjectInterface.php
typo3/sysext/extbase/Classes/Persistence/Backend.php
typo3/sysext/extbase/Classes/Persistence/LazyObjectStorage.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/ObjectMonitoringInterface.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/ObjectStorage.php
typo3/sysext/extbase/Tests/DomainObject/AbstractEntity_testcase.php
typo3/sysext/extbase/ext_autoload.php

index 2a5f020..ef8ef87 100644 (file)
@@ -31,7 +31,7 @@
  * @subpackage DomainObject
  * @version $ID:$
  */
-abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbase_DomainObject_DomainObjectInterface {
+abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbase_DomainObject_DomainObjectInterface, Tx_Extbase_Persistence_ObjectMonitoringInterface {
 
        /**
         * @var int The uid
index 69d57d4..04909bf 100644 (file)
@@ -35,7 +35,7 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
        /**
         * @var An array holding the clean property values. Set right after reconstitution of the object
         */
-       private $_cleanProperties = array();
+       private $_cleanProperties;
 
        /**
         * Register an object's clean state, e.g. after it has been reconstituted
@@ -66,6 +66,9 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
         */
        public function _memorizePropertyCleanState($propertyName) {
                $propertyValue = $this->$propertyName;
+               if (!is_array($this->_cleanProperties)) {
+                       $this->_cleanProperties = array();
+               }
                if (is_object($propertyValue)) {
                        $this->_cleanProperties[$propertyName] = clone($propertyValue);
 
@@ -114,35 +117,40 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
         * @return boolean
         */
        public function _isDirty($propertyName = NULL) {
-               if (empty($this->_cleanProperties)) return TRUE;
-               // if (!is_array($this->_cleanProperties)) throw new Tx_Extbase_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before asking _isDirty().', 1233309106);
-               if ($this->uid !== NULL && $this->uid != $this->_cleanProperties['uid']) throw new Tx_Extbase_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
+               if ($this->uid !== NULL && is_array($this->_cleanProperties) && $this->uid != $this->_getCleanProperty('uid')) throw new Tx_Extbase_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
+               if ($propertyName === NULL) {
+                       foreach ($this->_getCleanProperties() as $propertyName => $cleanPropertyValue) {
+                               if ($this->isPropertyDirty($cleanPropertyValue, $this->$propertyName) === TRUE) return TRUE;
+                       }
+               } else {
+                       if ($this->isPropertyDirty($this->_getCleanProperty($propertyName), $this->$propertyName) === TRUE) return TRUE;
+               }
+               return FALSE;
+       }
+
+       /**
+        * Checks the $value against the $cleanState.
+        *
+        * @param mixed $previousValue
+        * @param mixed $currentValue
+        * @return boolan
+        */
+       protected function isPropertyDirty($previousValue, $currentValue) {
                $result = FALSE;
-               if ($propertyName !== NULL) {
-                       if (is_object($this->$propertyName)) {
-                               // In case it is an object, we do a simple comparison (!=) as we want cloned objects to return the same values.
-                               $result = $this->_cleanProperties[$propertyName] != $this->$propertyName;
+               // In case it is an object and it implements the ObjectMonitoringInterface, we call _isDirty() instead of a simple comparison of objects.
+               // We do this, because if the object itself contains a lazy loaded property, the comparison of the objects might fail even if the object didn't change
+               if (is_object($currentValue)) {
+                       if ($currentValue instanceof Tx_Extbase_Persistence_ObjectMonitoringInterface) {
+                               $result = !is_object($previousValue) || $currentValue->_isDirty() || (get_class($previousValue) !== get_class($currentValue));
                        } else {
-                               $result = $this->_cleanProperties[$propertyName] !== $this->$propertyName;
+                               // For all other objects we do only a simple comparison (!=) as we want cloned objects to return the same values.
+                               $result = ($previousValue != $currentValue);
                        }
                } else {
-                       foreach ($this->_cleanProperties as $propertyName => $propertyValue) {
-                               if (is_object($this->$propertyName)) {
-                                       // In case it is an object, we do a simple comparison (!=) as we want cloned objects to return the same values.
-                                       if ($this->$propertyName != $propertyValue) {
-                                               $result = TRUE;
-                                               break;
-                                       }
-                               } else {
-                                       if ($this->$propertyName !== $propertyValue) {
-                                               $result = TRUE;
-                                               break;
-                                       }
-                               }
-                       }
+                       $result = ($previousValue !== $currentValue);
                }
                return $result;
        }
-
+       
 }
 ?>
\ No newline at end of file
index a104723..34c43dc 100644 (file)
@@ -44,14 +44,6 @@ interface Tx_Extbase_DomainObject_DomainObjectInterface {
        public function getUid();
 
        /**
-        * Register an object's clean state, e.g. after it has been reconstituted
-        * from the database
-        *
-        * @return void
-        */
-       public function _memorizeCleanState();
-
-       /**
         * Returns TRUE if the object is new (the uid was not set, yet). Only for internal use
         *
         * @return boolean
@@ -59,13 +51,6 @@ interface Tx_Extbase_DomainObject_DomainObjectInterface {
        public function _isNew();
 
        /**
-        * Returns TRUE if the properties were modified after reconstitution
-        *
-        * @return boolean
-        */
-       public function _isDirty();
-
-       /**
         * Reconstitutes a property. Only for internal use.
         *
         * @param string $propertyName
index 309f603..de911ac 100644 (file)
@@ -46,6 +46,11 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        protected $aggregateRootObjects;
 
        /**
+        * @var Tx_Extbase_Persistence_ObjectStorage
+        */
+       protected $visitedDuringPersistence;
+
+       /**
         * @var Tx_Extbase_Persistence_IdentityMap
         **/
        protected $identityMap;
@@ -335,6 +340,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @return void
         */
        protected function persistObjects() {
+               $this->visitedDuringPersistence = new Tx_Extbase_Persistence_ObjectStorage();
                foreach ($this->aggregateRootObjects as $object) {
                        if (!$this->identityMap->hasObject($object)) {
                                $this->insertObject($object);
@@ -354,6 +360,12 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @return void
         */
        protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               if (isset($this->visitedDuringPersistence[$object])) {
+                       return $this->visitedDuringPersistence[$object];
+               } else {
+                       $this->visitedDuringPersistence[$object] = $object->getUid();
+               }
+
                $row = array();
                $queue = array();
                $className = get_class($object);
@@ -387,8 +399,8 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                                                        $this->persistValueObject($propertyValue);
                                                }
                                        }
-                                       $queue[] = $propertyValue;
                                        $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
+                                       $queue[] = $propertyValue;
                                }
                        } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
                                $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
index 5eaaa6f..ea11a60 100644 (file)
@@ -97,6 +97,7 @@ class Tx_Extbase_Persistence_LazyObjectStorage extends Tx_Extbase_Persistence_Ob
                        foreach ($objects as $object) {
                                parent::attach($object);
                        }
+                       $this->_memorizeCleanState();
                        $this->parentObject->_memorizeCleanState($this->propertyName);
                }
        }
index 2c2721d..aef0994 100644 (file)
@@ -256,6 +256,7 @@ class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton {
                                        case 'SplObjectStorage':
                                        case 'Tx_Extbase_Persistence_ObjectStorage':
                                                $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $this->fetchRelated($object, $propertyName, $row[$columnName]));
+                                               $propertyValue->_memorizeCleanState();
                                        break;
                                        default:
                                                if (($propertyData['type'] === 'DateTime') || in_array('DateTime', class_parents($propertyData['type']))) {
diff --git a/typo3/sysext/extbase/Classes/Persistence/ObjectMonitoringInterface.php b/typo3/sysext/extbase/Classes/Persistence/ObjectMonitoringInterface.php
new file mode 100644 (file)
index 0000000..9320216
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * An interface how to monitor changes on an object and its properties. All domain objects which should be persisted need to implement the below interface.
+ *
+ * @see Tx_Extbase_DomainObject_AbstractEntity
+ * @see Tx_Extbase_DomainObject_AbstractValueObject
+ *
+ * @package Extbase
+ * @subpackage Persistence
+ * @version $ID:$
+ */
+interface Tx_Extbase_Persistence_ObjectMonitoringInterface {
+
+       /**
+        * Register an object's clean state, e.g. after it has been reconstituted
+        * from the database
+        *
+        * @return void
+        */
+       public function _memorizeCleanState();
+
+       /**
+        * Returns TRUE if the properties were modified after reconstitution
+        *
+        * @return boolean
+        */
+       public function _isDirty();
+       
+}
+?>
\ No newline at end of file
index 4a9dfb6..4280a42 100644 (file)
@@ -32,7 +32,7 @@
  * @subpackage Persistence
  * @version $ID:$
  */
-class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, ArrayAccess {
+class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, ArrayAccess, Tx_Extbase_Persistence_ObjectMonitoringInterface {
 
        /**
         * An array holding the objects and the stored information. The key of the array items ist the 
@@ -51,6 +51,12 @@ class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, Array
        protected $storage = array();
 
        /**
+        * A flag indication if the object storage was modified after reconstitution (eg. by adding a new object)
+        * @var bool
+        */
+       protected $isModified = FALSE;
+               
+       /**
         * Rewind the iterator to the first storage element.
         *
         * @return void
@@ -114,6 +120,7 @@ class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, Array
         * @return void
         */
        public function offsetSet($object, $information) {
+               $this->isModified = TRUE;
                $this->storage[spl_object_hash($object)] = array('obj' => $object, 'inf' => $information);
        }
 
@@ -134,6 +141,7 @@ class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, Array
         * @return void
         */
        public function offsetUnset($object) {
+               $this->isModified = TRUE;
                unset($this->storage[spl_object_hash($object)]);
        }
 
@@ -189,6 +197,7 @@ class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, Array
        }
        
        public function setInfo($data) {
+               $this->isModified = TRUE;
                $key = key($this->storage);
                $this->storage[$key]['inf']  = $data;
        }
@@ -238,6 +247,25 @@ class Tx_Extbase_Persistence_ObjectStorage implements Countable, Iterator, Array
                throw new RuntimeException('A ObjectStorage instance cannot be unserialized.', 1267700870);
        }
        
+       /**
+        * Register an object's clean state, e.g. after it has been reconstituted
+        * from the database
+        *
+        * @return void
+        */
+       public function _memorizeCleanState() {
+               $this->isModified = FALSE;
+       }
+
+       /**
+        * Returns TRUE if the properties were modified after reconstitution
+        *
+        * @return boolean
+        */
+       public function _isDirty() {
+               return $this->isModified;
+       }
+       
 }
 
 ?>
\ No newline at end of file
index f43d2aa..b2ac697 100644 (file)
@@ -36,9 +36,8 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
                $domainObject = new $domainObjectName();
                $domainObject->foo = 'Test';
                $domainObject->bar = 'It is raining outside';
+               $domainObject->_memorizeCleanState();
                
-               $domainObject->_memorizePropertyCleanState('foo');
-               $domainObject->_memorizePropertyCleanState('bar');
                $this->assertFalse($domainObject->_isDirty());
        }
 
@@ -55,9 +54,9 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
                $domainObject->foo = 'Test';
                $domainObject->bar = 'It is raining outside';
 
-               $domainObject->_memorizePropertyCleanState('foo');
-               $domainObject->_memorizePropertyCleanState('bar');
+               $domainObject->_memorizeCleanState();
                $domainObject->bar = 'Now it is sunny.';
+
                $this->assertTrue($domainObject->_isDirty());
        }
 
@@ -73,9 +72,8 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
                $domainObject = new $domainObjectName();
                $domainObject->foo = new DateTime();
                $domainObject->bar = 'It is raining outside';
+               $domainObject->_memorizeCleanState();
 
-               $domainObject->_memorizePropertyCleanState('foo');
-               $domainObject->_memorizePropertyCleanState('bar');
                $this->assertFalse($domainObject->_isDirty());
        }
 
@@ -94,13 +92,15 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
                        public $foo;
                        public $bar;
                }');
+               $secondDomainObject = new $secondDomainObjectName;
+               $secondDomainObject->_memorizeCleanState();
+               
 
                $domainObject = new $domainObjectName();
-               $domainObject->foo = new $secondDomainObjectName;
+               $domainObject->foo = $secondDomainObject;
                $domainObject->bar = 'It is raining outside';
+               $domainObject->_memorizeCleanState();
 
-               $domainObject->_memorizePropertyCleanState('foo');
-               $domainObject->_memorizePropertyCleanState('bar');
                $this->assertFalse($domainObject->_isDirty());
        }
 }
index 9a99fbb..e8d2032 100644 (file)
@@ -91,6 +91,7 @@ return array(
        'tx_extbase_persistence_lazyloadingproxy' => $extensionClassesPath . 'Persistence/LazyLoadingProxy.php',
        'tx_extbase_persistence_lazyobjectstorage' => $extensionClassesPath . 'Persistence/LazyObjectStorage.php',
        'tx_extbase_persistence_loadingstrategyinterface' => $extensionClassesPath . 'Persistence/LoadingStrategyInterface.php',
+       'tx_extbase_persistence_objectmonitoringinterface' => $extensionClassesPath . 'Persistence/ObjectMonitoringInterface.php',
        'tx_extbase_persistence_manager' => $extensionClassesPath . 'Persistence/Manager.php',
        'tx_extbase_persistence_managerinterface' => $extensionClassesPath . 'Persistence/ManagerInterface.php',
        'tx_extbase_persistence_objectstorage' => $extensionClassesPath . 'Persistence/ObjectStorage.php',