[+BUGFIX] Extbase: Some smaller tweaks and fixes
authorBastian Waidelich <bastian@typo3.org>
Tue, 2 Nov 2010 18:20:52 +0000 (18:20 +0000)
committerBastian Waidelich <bastian@typo3.org>
Tue, 2 Nov 2010 18:20:52 +0000 (18:20 +0000)
[!!!][+FEATURE] Extbase (Persistence): Backport QueryResult from FLOW3

Now Query::execute() returns an instance of QueryResultInterface that allows it to modify the query before actually accessing the records that it retrieves. This is required for the upcoming "Fluid widgets" backport (#10568).
NOTE: This change is not backwards compatible, if you work with PHPs array_* functions on the query result. To work around this issue, you'll have to convert the query result to an array before by calling the QueryResult::toArray() method. We're planning to add a compatibility mode, but that's not yet implemented.

Resolves: #10566

13 files changed:
typo3/sysext/extbase/Classes/MVC/Controller/Argument.php
typo3/sysext/extbase/Classes/Persistence/Backend.php
typo3/sysext/extbase/Classes/Persistence/ManagerInterface.php
typo3/sysext/extbase/Classes/Persistence/Query.php
typo3/sysext/extbase/Classes/Persistence/QueryInterface.php
typo3/sysext/extbase/Classes/Persistence/QueryResult.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/QueryResultInterface.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/QuerySettingsInterface.php
typo3/sysext/extbase/Classes/Property/Mapper.php
typo3/sysext/extbase/Classes/Utility/Extension.php
typo3/sysext/extbase/Tests/Persistence/QueryResult_testcase.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Persistence/Query_testcase.php
typo3/sysext/extbase/Tests/Persistence/Repository_testcase.php [new file with mode: 0644]

index bfbcaeb..b2d9b1a 100644 (file)
@@ -399,12 +399,10 @@ class Tx_Extbase_MVC_Controller_Argument {
                $query = $this->queryFactory->create($this->dataType);
                $query->getQuerySettings()->setRespectSysLanguage(FALSE);
                $query->getQuerySettings()->setRespectStoragePage(FALSE);
-               $result = $query->matching($query->equals('uid', $uid))->execute();
-               $object = NULL;
-               if (count($result) > 0) {
-                       $object = current($result);
-               }
-               return $object;
+               return $query->matching(
+                       $query->equals('uid', $uid))
+                       ->execute()
+                       ->getFirst();
        }
 
        /**
index 7b36d48..9fc69cd 100644 (file)
@@ -159,7 +159,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) {
                $this->queryFactory = $queryFactory;
        }
-       
+
        /**
         * Injects the QueryObjectModelFactory
         *
@@ -196,7 +196,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        public function getQomFactory() {
                return $this->qomFactory;
        }
-       
+
        /**
         * Returns the current identityMap
         *
@@ -265,12 +265,10 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                        return $this->identityMap->getObjectByIdentifier($identifier, $className);
                } else {
                        $query = $this->queryFactory->create($className);
-                       $result = $query->matching($query->withUid($identifier))->execute();
-                       $object = NULL;
-                       if (count($result) > 0) {
-                               $object = current($result);
-                       }
-                       return $object;
+                       return $query->matching(
+                               $query->withUid($identifier))
+                               ->execute()
+                               ->getFirst();
                }
        }
 
@@ -374,11 +372,11 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
                $classSchema = $this->reflectionService->getClassSchema($className);
-               
+
                $properties = $object->_getProperties();
                foreach ($properties as $propertyName => $propertyValue) {
                        if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
-                       
+
                        $columnMap = $dataMap->getColumnMap($propertyName);
                        $propertyMetaData = $classSchema->getProperty($propertyName);
                        $propertyType = $propertyMetaData['type'];
@@ -413,7 +411,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                if (count($row) > 0) {
                        $this->updateObject($object, $row);
                }
-               
+
                if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
                        $object->_memorizeCleanState();
                }
@@ -421,7 +419,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                foreach ($queue as $queuedObject) {
                        $this->persistObject($queuedObject);
                }
-               
+
        }
 
        /**
@@ -440,13 +438,13 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
 
                if (is_object($value)) {
                        if (!($value instanceof $expectedType)) {
-                               throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);                                
+                               throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
                        }
-               } elseif ($expectedType !== gettype($value)) {                  
+               } elseif ($expectedType !== gettype($value)) {
                        throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
                }
        }
-       
+
        /**
         * Checks, if the property value is lazy loaded and was not initialized
         *
@@ -462,7 +460,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                }
                return FALSE;
        }
-       
+
        /**
         * Persists the given value object.
         *
@@ -492,7 +490,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                        $this->insertObject($object, $row);
                }
        }
-       
+
        /**
         * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
         *
@@ -501,7 +499,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
                return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
        }
-       
+
        /**
         * Persists a an object storage. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
         * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
@@ -509,13 +507,13 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
         * @param string $propertyName The name of the property the related objects are stored in
-        * @param mixed $propertyValue The property value 
+        * @param mixed $propertyValue The property value
         * @return void
         */
        protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
                $className = get_class($parentObject);
                $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
-               $columnName = $columnMap->getColumnName();              
+               $columnName = $columnMap->getColumnName();
                $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
 
                foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
@@ -544,14 +542,14 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                        $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
                        $sortingPosition++;
                }
-               
+
                if ($columnMap->getParentKeyFieldName() === NULL) {
                        $row[$columnMap->getColumnName()] = implode(',', $currentUids);
                } else {
                        $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
                }
        }
-       
+
        /**
         * Returns the current field value of the given object property from the storage backend.
         *
@@ -564,12 +562,14 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
                $query = $this->queryFactory->create($className);
                $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
-               $rows = $query->matching($query->withUid($object->getUid()))->execute();
-               $currentRow = current($rows);
+               $currentRow = $query->matching(
+                       $query->withUid($object->getUid()))
+                       ->execute()
+                       ->getFirst();
                $fieldValue = $currentRow[$columnMap->getColumnName()];
                return $fieldValue;
        }
-       
+
        /**
         * Returns the removed objects determined by a comparison of the clean property value
         * with the actual property value.
@@ -591,13 +591,13 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                }
                return $removedObjects;
        }
-       
+
        /**
         * Updates the fields defining the relation between the object and the parent object.
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object 
-        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject 
-        * @param string $parentPropertyName 
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
+        * @param string $parentPropertyName
         * @return void
         */
        protected function attachObjectToParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
@@ -619,18 +619,18 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                        }
                        if (count($row) > 0) {
                                $this->updateObject($object, $row);
-                       }                       
+                       }
                } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
                        $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
                }
        }
-       
+
        /**
         * Updates the fields defining the relation between the object and the parent object.
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object 
-        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject 
-        * @param string $parentPropertyName 
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
+        * @param string $parentPropertyName
         * @return void
         */
        protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
@@ -657,7 +657,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                        $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
                }
        }
-       
+
        /**
         * Inserts an object in the storage backend
         *
@@ -680,7 +680,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                }
                $this->identityMap->registerObject($object, $uid);
        }
-       
+
        /**
         * Inserts mm-relation into a relation table
         *
@@ -706,7 +706,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                if ($columnMap->getRelationTablePageIdColumnName() !== NULL) {
                        $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
                }
-               
+
                $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
                if (count($relationTableInsertFields)) {
                        foreach($relationTableInsertFields as $insertField => $insertValue) {
@@ -798,7 +798,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                }
                return $res;
        }
-       
+
        /**
         * Returns a table row to be inserted or updated in the database
         *
@@ -866,9 +866,9 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $this->removeRelatedObjects($object);
                if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
                        $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
-               }               
+               }
        }
-       
+
        /**
         * Remove related objects
         *
@@ -879,7 +879,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
                $classSchema = $this->reflectionService->getClassSchema($className);
-                               
+
                $properties = $object->_getProperties();
                foreach ($properties as $propertyName => $propertyValue) {
                        $columnMap = $dataMap->getColumnMap($propertyName);
@@ -889,7 +889,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                                        foreach ($propertyValue as $containedObject) {
                                                $this->removeObject($containedObject);
                                        }
-                               } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {                            
+                               } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
                                        $this->removeObject($propertyValue);
                                }
                        }
@@ -917,7 +917,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
                return (int) $storagePidList[0];
        }
-       
+
        /**
         * Returns a plain value, i.e. objects are flattened out if possible.
         *
index b36e084..f167e0b 100644 (file)
@@ -57,5 +57,31 @@ interface Tx_Extbase_Persistence_ManagerInterface {
         * @api
         */
        public function persistAll();
+
+       /**
+        * Returns the number of records matching the query.
+        *
+        * @param Tx_Extbase_Persistence_QueryInterface $query
+        * @return integer
+        * @api
+        */
+       public function getObjectCountByQuery(Tx_Extbase_Persistence_QueryInterface $query);
+
+       /**
+        * Returns the object data matching the $query.
+        *
+        * @param Tx_Extbase_Persistence_QueryInterface $query
+        * @return array
+        * @api
+        */
+       public function getObjectDataByQuery(Tx_Extbase_Persistence_QueryInterface $query);
+
+       /**
+        * Registers a repository
+        *
+        * @param string $className The class name of the repository to be reigistered
+        * @return void
+        */
+       public function registerRepositoryClassName($className);
 }
 ?>
\ No newline at end of file
index 6d6747e..c75818d 100644 (file)
@@ -42,12 +42,17 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        protected $type;
 
        /**
+        * @var Tx_Extbase_Object_ObjectManagerInterface
+        */
+       protected $objectManager;
+
+       /**
         * @var Tx_Extbase_Persistence_DataMapper
         */
        protected $dataMapper;
 
        /**
-        * @var Tx_Extbase_Persistence_Manager
+        * @var Tx_Extbase_Persistence_ManagerInterface
         */
        protected $persistenceManager;
 
@@ -103,6 +108,14 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        }
 
        /**
+        * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager
+        * @return void
+        */
+       public function injectObjectManager(Tx_Extbase_Object_ObjectManagerInterface $objectManager) {
+               $this->objectManager = $objectManager;
+       }
+
+       /**
         * Injects the persistence manager, used to fetch the CR session
         *
         * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
@@ -146,7 +159,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
 
        /**
         * Returns the Query Settings.
-        * 
+        *
         * @return Tx_Extbase_Persistence_QuerySettingsInterface $querySettings The Query Settings
         * @api This method is not part of FLOW3 API
         */
@@ -173,7 +186,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        public function setSource(Tx_Extbase_Persistence_QOM_SourceInterface $source) {
                $this->source = $source;
        }
-       
+
        /**
         * Returns the selectorn name or an empty string, if the source is not a selector
         * // TODO This has to be checked at another place
@@ -202,28 +215,29 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        /**
         * Executes the query against the database and returns the result
         *
-        * @return array<object> The query result as an array of objects
+        * @return Tx_Extbase_Persistence_QueryResultInterface|array The query result object or an array if $this->getQuerySettings()->getReturnRawQueryResult() is TRUE
         * @api
         */
        public function execute() {
-               $rows = $this->persistenceManager->getObjectDataByQuery($this);
                if ($this->getQuerySettings()->getReturnRawQueryResult() === TRUE) {
-                       return $rows;
+                       return $this->persistenceManager->getObjectDataByQuery($this);
                } else {
-                       return $this->dataMapper->map($this->getType(), $rows);
+                       return $this->objectManager->create('Tx_Extbase_Persistence_QueryResultInterface', $this);
                }
        }
-       
+
        /**
         * Executes the number of matching objects for the query
         *
         * @return integer The number of matching objects
+        * @deprecated since Extbase 1.3.0; was removed in FLOW3; will be removed in Extbase 1.4.0; use Query::execute()::count() instead
         * @api
         */
        public function count() {
+               t3lib_div::logDeprecatedFunction();
                return $this->persistenceManager->getObjectCountByQuery($this);
        }
-       
+
        /**
         * Sets the property names to order the result by. Expected like this:
         * array(
@@ -240,7 +254,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                $this->orderings = $orderings;
                return $this;
        }
-       
+
        /**
         * Returns the property names to order the result by. Like this:
         * array(
@@ -302,7 +316,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        public function getOffset() {
                return $this->offset;
        }
-       
+
        /**
         * The constraint used to limit the result set. Returns $this to allow
         * for chaining (fluid interface)
@@ -328,7 +342,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                $this->statement = $this->qomFactory->statement($statement, $parameters);
                return $this;
        }
-       
+
        /**
         * Returns the statement of this query.
         *
@@ -337,7 +351,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
        public function getStatement() {
                return $this->statement;
        }
-       
+
        /**
         * Gets the constraint for this query.
         *
@@ -375,7 +389,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                }
                return $resultingConstraint;
        }
-       
+
        /**
         * Performs a logical disjunction of the two given constraints
         *
@@ -474,7 +488,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                        $operand
                        );
        }
-       
+
        /**
         * Returns a "contains" criterion used for matching objects against a query.
         * It matches if the multivalued property contains the given operand.
@@ -505,7 +519,7 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                if (!is_array($operand) && (!$operand instanceof ArrayAccess) && (!$operand instanceof Traversable)) {
                        throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('The "in" operator must be given a mutlivalued operand (array, ArrayAccess, Traversable).', 1264678095);
                }
-               
+
                return $this->qomFactory->comparison(
                        $this->qomFactory->propertyValue($propertyName, $this->getSelectorName()),
                        Tx_Extbase_Persistence_QueryInterface::OPERATOR_IN,
@@ -576,6 +590,6 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
                        $operand
                        );
        }
-               
+
 }
 ?>
\ No newline at end of file
index 47c91a5..f23343b 100644 (file)
@@ -113,7 +113,7 @@ interface Tx_Extbase_Persistence_QueryInterface {
        /**
         * Executes the query against the backend and returns the result
         *
-        * @return array<object> The query result as an array of objects
+        * @return Tx_Extbase_Persistence_QueryResultInterface|array The query result object or an array if $this->getQuerySettings()->getReturnRawQueryResult() is TRUE
         * @api
         */
        public function execute();
@@ -288,5 +288,30 @@ interface Tx_Extbase_Persistence_QueryInterface {
         */
        public function greaterThanOrEqual($propertyName, $operand);
 
+       /**
+        * Returns the type this query cares for.
+        *
+        * @return string
+        * @api
+        */
+       public function getType();
+
+       /**
+        * Sets the Query Settings. These Query settings must match the settings expected by
+        * the specific Storage Backend.
+        *
+        * @param Tx_Extbase_Persistence_QuerySettingsInterface $querySettings The Query Settings
+        * @return void
+        * @api This method is not part of FLOW3 API
+        */
+       public function setQuerySettings(Tx_Extbase_Persistence_QuerySettingsInterface $querySettings);
+
+       /**
+        * Returns the Query Settings.
+        *
+        * @return Tx_Extbase_Persistence_QuerySettingsInterface $querySettings The Query Settings
+        * @api This method is not part of FLOW3 API
+        */
+       public function getQuerySettings();
 }
 ?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Persistence/QueryResult.php b/typo3/sysext/extbase/Classes/Persistence/QueryResult.php
new file mode 100644 (file)
index 0000000..b1e09e3
--- /dev/null
@@ -0,0 +1,246 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 Bastian Waidelich <bastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * A lazy result list that is returned by Query::execute()
+ *
+ * @package Extbase
+ * @subpackage Persistence
+ * @scope prototype
+ * @api
+ */
+class Tx_Extbase_Persistence_QueryResult implements Tx_Extbase_Persistence_QueryResultInterface {
+
+       /**
+        * @var Tx_Extbase_Persistence_Mapper_DataMapper
+        */
+       protected $dataMapper;
+
+       /**
+        * @var Tx_Extbase_Persistence_ManagerInterface
+        */
+       protected $persistenceManager;
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryInterface
+        */
+       protected $query;
+
+       /**
+        * @var array
+        * @transient
+        */
+       protected $queryResult;
+
+       /**
+        * Constructor
+        *
+        * @param Tx_Extbase_Persistence_QueryInterface $query
+        */
+       public function __construct(Tx_Extbase_Persistence_QueryInterface $query) {
+               $this->query = $query;
+       }
+
+       /**
+        * Injects the DataMapper to map records to objects
+        *
+        * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
+        * @return void
+        */
+       public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
+               $this->dataMapper = $dataMapper;
+       }
+
+       /**
+        * Injects the persistence manager
+        *
+        * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
+        * @return void
+        */
+       public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface $persistenceManager) {
+               $this->persistenceManager = $persistenceManager;
+       }
+
+       /**
+        * Loads the objects this QueryResult is supposed to hold
+        *
+        * @return void
+        */
+       protected function initialize() {
+               if (!is_array($this->queryResult)) {
+                       $this->queryResult = $this->dataMapper->map($this->query->getType(), $this->persistenceManager->getObjectDataByQuery($this->query));
+               }
+       }
+
+       /**
+        * Returns a clone of the query object
+        *
+        * @return Tx_Extbase_Persistence_QueryInterface
+        * @api
+        */
+       public function getQuery() {
+               return clone $this->query;
+       }
+
+       /**
+        * Returns the first object in the result set
+        *
+        * @return object
+        * @api
+        */
+       public function getFirst() {
+               if (is_array($this->queryResult)) {
+                       $queryResult = $this->queryResult;
+                       reset($queryResult);
+                       return current($queryResult);
+               } else {
+                       $query = clone $this->query;
+                       $query->setLimit(1);
+                       $queryResult = $this->dataMapper->map($this->query->getType(), $this->persistenceManager->getObjectDataByQuery($this->query));
+                       return current($queryResult);
+               }
+       }
+
+       /**
+        * Returns the number of objects in the result
+        *
+        * @return integer The number of matching objects
+        * @api
+        */
+       public function count() {
+               if (is_array($this->queryResult)) {
+                       return count($this->queryResult);
+               } else {
+                       return $this->persistenceManager->getObjectCountByQuery($this->query);
+               }
+       }
+
+       /**
+        * Returns an array with the objects in the result set
+        *
+        * @return array
+        * @api
+        */
+       public function toArray() {
+               $this->initialize();
+               return iterator_to_array($this);
+       }
+
+       /**
+        * This method is needed to implement the ArrayAccess interface,
+        * but it isn't very useful as the offset has to be an integer
+        *
+        * @param mixed $offset
+        * @return boolean
+        * @see ArrayAccess::offsetExists()
+        */
+       public function offsetExists($offset) {
+               $this->initialize();
+               return isset($this->queryResult[$offset]);
+       }
+
+       /**
+        * @param mixed $offset
+        * @return mixed
+        * @see ArrayAccess::offsetGet()
+        */
+       public function offsetGet($offset) {
+               $this->initialize();
+               return isset($this->queryResult[$offset]) ? $this->queryResult[$offset] : NULL;
+       }
+
+       /**
+        * This method has no effect on the persisted objects but only on the result set
+        *
+        * @param mixed $offset
+        * @param mixed $value
+        * @return void
+        * @see ArrayAccess::offsetSet()
+        */
+       public function offsetSet($offset, $value) {
+               $this->initialize();
+               $this->queryResult[$offset] = $value;
+       }
+
+       /**
+        * This method has no effect on the persisted objects but only on the result set
+        *
+        * @param mixed $offset
+        * @return void
+        * @see ArrayAccess::offsetUnset()
+        */
+       public function offsetUnset($offset) {
+               $this->initialize();
+               unset($this->queryResult[$offset]);
+       }
+
+       /**
+        * @return mixed
+        * @see Iterator::current()
+        */
+       public function current() {
+               $this->initialize();
+               return current($this->queryResult);
+       }
+
+       /**
+        * @return mixed
+        * @see Iterator::key()
+        */
+       public function key() {
+               $this->initialize();
+               return key($this->queryResult);
+       }
+
+       /**
+        * @return void
+        * @see Iterator::next()
+        */
+       public function next() {
+               $this->initialize();
+               next($this->queryResult);
+       }
+
+       /**
+        * @return void
+        * @see Iterator::rewind()
+        */
+       public function rewind() {
+               $this->initialize();
+               reset($this->queryResult);
+       }
+
+       /**
+        * @return void
+        * @see Iterator::valid()
+        */
+       public function valid() {
+               $this->initialize();
+               return current($this->queryResult) !== FALSE;
+       }
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Persistence/QueryResultInterface.php b/typo3/sysext/extbase/Classes/Persistence/QueryResultInterface.php
new file mode 100644 (file)
index 0000000..c4d12f1
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 Bastian Waidelich <bastian@typo3.org>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3.
+*  All credits go to the v5 team.
+*
+*  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!
+***************************************************************/
+
+/**
+ * A lazy result list that is returned by Query::execute()
+ *
+ * @package Extbase
+ * @subpackage Persistence
+ */
+interface Tx_Extbase_Persistence_QueryResultInterface extends Countable, Iterator, ArrayAccess {
+
+       /**
+        * Returns a clone of the query object
+        *
+        * @return Tx_Extbase_Persistence_QueryInterface
+        * @api
+        */
+       public function getQuery();
+
+       /**
+        * Returns the first object in the result set
+        *
+        * @return object
+        * @api
+        */
+       public function getFirst();
+
+       /**
+        * Returns an array with the objects in the result set
+        *
+        * @return array
+        * @api
+        */
+       public function toArray();
+}
+?>
\ No newline at end of file
index e950ad4..9a8b686 100644 (file)
@@ -5,7 +5,7 @@
 *  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
 *  All rights reserved
 *
-*  This class is a backport of the corresponding class of FLOW3. 
+*  This class is a backport of the corresponding class of FLOW3.
 *  All credits go to the v5 team.
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
  */
 interface Tx_Extbase_Persistence_QuerySettingsInterface {
 
+       /**
+        * Sets the flag if the storage page should be respected for the query.
+        *
+        * @param $respectStoragePage If TRUE the storage page ID will be determined and the statement will be extended accordingly.
+        * @return $this (fluent interface)
+        * @api
+        */
+       public function setRespectStoragePage($respectStoragePage);
+
+       /**
+        * Returns the state, if the storage page should be respected for the query.
+        *
+        * @return boolean TRUE, if the storage page should be respected; otherwise FALSE.
+        */
+       public function getRespectStoragePage();
+
+       /**
+        * Sets the flag if a  and language overlay should be performed.
+        *
+        * @param $respectEnableFields TRUE if a  and language overlay should be performed.
+        * @return $this (fluent interface)
+        * @api
+        */
+       public function setRespectSysLanguage($respectSysLanguage);
+
+       /**
+        * Returns the state, if a  and language overlay should be performed.
+        *
+        * @return boolean TRUE, if a  and language overlay should be performed; otherwise FALSE.
+        */
+       public function getRespectSysLanguage();
+
+       /**
+        * Sets the flag if the visibility in the frontend should be respected.
+        *
+        * @param $respectEnableFields TRUE if the visibility in the frontend should be respected. If TRUE, the "enable fields" of TYPO3 will be added to the query statement.
+        * @return $this (fluent interface)
+        * @api
+        */
+       public function setRespectEnableFields($respectEnableFields);
+
+       /**
+        * Returns the state, if the visibility settings for the frontend should be respected for the query.
+        *
+        * @return boolean TRUE, if the visibility settings for the frontend should should be respected; otherwise FALSE.
+        */
+       public function getRespectEnableFields();
+
+       /**
+        * Sets the state, if the QueryResult should be returned unmapped.
+        *
+        * @var boolean $returnRawQueryResult TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        * @return void
+        */
+       public function setReturnRawQueryResult($returnRawQueryResult);
+
+       /**
+        * Returns the state, if the QueryResult should be returned unmapped.
+        *
+        * @return boolean TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        */
+       public function getReturnRawQueryResult();
+
 }
 ?>
\ No newline at end of file
index 3ea0672..f55e8fb 100644 (file)
@@ -124,8 +124,8 @@ class Tx_Extbase_Property_Mapper implements t3lib_Singleton {
         * @param array $propertyNames Names of the properties to map.
         * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object.
         * @param object $target The target object
-        * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued.
         * @param Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator A validator used for validating the target object
+        * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued.
         * @return boolean TRUE if the mapped properties are valid, otherwise FALSE
         * @see getMappingResults()
         * @see map()
@@ -331,12 +331,10 @@ class Tx_Extbase_Property_Mapper implements t3lib_Singleton {
                $query = $this->queryFactory->create($dataType);
                $query->getQuerySettings()->setRespectSysLanguage(FALSE);
                $query->getQuerySettings()->setRespectStoragePage(FALSE);
-               $result = $query->matching($query->equals('uid', intval($uid)))->execute();
-               $object = NULL;
-               if (count($result) > 0) {
-                       $object = current($result);
-               }
-               return $object;
+               return $query->matching(
+                       $query->equals('uid', intval($uid)))
+                       ->execute()
+                       ->getFirst();
        }
 }
 
index abc34c2..ce0dd43 100644 (file)
@@ -551,7 +551,7 @@ tt_content.list.20.' . $pluginSignature . ' {
                                2
                        );
                        if (count($pages) > 1) {
-                               throw new Tx_Extbase_Exception('There is more than one "' . $pluginSignature . '" plugin in the current page tree. Please remove one plugin or set the TypoScript configuration "plugin.' . $pluginSignature . '.view.defaultPid" to a fixed page id' , 1280773643);
+                               throw new Tx_Extbase_Exception('There is more than one "' . $pluginSignature . '" plugin in the current page tree. Please remove one plugin or set the TypoScript configuration "plugin.tx_' . $pluginSignature . '.view.defaultPid" to a fixed page id' , 1280773643);
                        }
                        return count($pages) > 0 ? $pages[0]['pid'] : NULL;
                }
diff --git a/typo3/sysext/extbase/Tests/Persistence/QueryResult_testcase.php b/typo3/sysext/extbase/Tests/Persistence/QueryResult_testcase.php
new file mode 100644 (file)
index 0000000..931a046
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 Bastian Waidelich <bastian@typo3.org>
+*  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!
+***************************************************************/
+
+class Tx_Extbase_Persistence_QueryResult_testcase extends Tx_Extbase_BaseTestCase {
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryResult
+        */
+       protected $queryResult;
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryInterface
+        */
+       protected $query;
+
+       /**
+        * @var Tx_Extbase_Persistence_ManagerInterface
+        */
+       protected $persistenceManager;
+
+       /**
+        * @var Tx_Extbase_Persistence_DataMapper
+        */
+       protected $dataMapper;
+
+       /**
+        * Sets up this test case
+        *
+        * @return void
+        */
+       public function setUp() {
+               $this->persistenceManager = $this->getMock('Tx_Extbase_Persistence_ManagerInterface');
+               $this->persistenceManager->expects($this->any())->method('getObjectDataByQuery')->will($this->returnValue(array('one', 'two')));
+               $this->persistenceManager->expects($this->any())->method('getObjectCountByQuery')->will($this->returnValue(2));
+               $this->dataMapper = $this->getMock('Tx_Extbase_Persistence_Mapper_DataMapper');
+               $this->query = $this->getMock('Tx_Extbase_Persistence_QueryInterface');
+               $this->queryResult = new Tx_Extbase_Persistence_QueryResult($this->query);
+               $this->queryResult->injectPersistenceManager($this->persistenceManager);
+               $this->queryResult->injectDataMapper($this->dataMapper);
+               $this->sampleResult = array(array('foo' => 'Foo1', 'bar' => 'Bar1'), array('foo' => 'Foo2', 'bar' => 'Bar2'));
+               $this->dataMapper->expects($this->any())->method('map')->will($this->returnValue($this->sampleResult));
+       }
+
+       /**
+        * @test
+        */
+       public function getQueryReturnsQueryObject() {
+               $this->assertType('Tx_Extbase_Persistence_QueryInterface', $this->queryResult->getQuery());
+       }
+
+       /**
+        * @test
+        */
+       public function getQueryReturnsAClone() {
+               $this->assertNotSame($this->query, $this->queryResult->getQuery());
+       }
+
+       /**
+        * @test
+        */
+       public function offsetExistsWorksAsExpected() {
+               $this->assertTrue($this->queryResult->offsetExists(0));
+               $this->assertFalse($this->queryResult->offsetExists(2));
+               $this->assertFalse($this->queryResult->offsetExists('foo'));
+       }
+
+       /**
+        * @test
+        */
+       public function offsetGetWorksAsExpected() {
+               $this->assertEquals(array('foo' => 'Foo1', 'bar' => 'Bar1'), $this->queryResult->offsetGet(0));
+               $this->assertNull($this->queryResult->offsetGet(2));
+               $this->assertNull($this->queryResult->offsetGet('foo'));
+       }
+
+       /**
+        * @test
+        */
+       public function offsetSetWorksAsExpected() {
+               $this->queryResult->offsetSet(0, array('foo' => 'FooOverridden', 'bar' => 'BarOverridden'));
+               $this->assertEquals(array('foo' => 'FooOverridden', 'bar' => 'BarOverridden'), $this->queryResult->offsetGet(0));
+       }
+
+       /**
+        * @test
+        */
+       public function offsetUnsetWorksAsExpected() {
+               $this->queryResult->offsetUnset(0);
+               $this->assertFalse($this->queryResult->offsetExists(0));
+       }
+
+       /**
+        * @test
+        */
+       public function countDoesNotInitializeProxy() {
+               $queryResult = $this->getMock('Tx_Extbase_Persistence_QueryResult', array('initialize'), array($this->query));
+               $queryResult->injectPersistenceManager($this->persistenceManager);
+               $queryResult->expects($this->never())->method('initialize');
+               $queryResult->count();
+       }
+
+       /**
+        * @test
+        */
+       public function countCallsGetObjectCountByQueryOnPersistenceManager() {
+               $queryResult = $this->getMock('Tx_Extbase_Persistence_QueryResult', array('initialize'), array($this->query));
+               $queryResult->injectPersistenceManager($this->persistenceManager);
+               $this->assertEquals(2, $queryResult->count());
+       }
+
+       /**
+        * @test
+        */
+       public function iteratorMethodsAreCorrectlyImplemented() {
+               $array1 = array('foo' => 'Foo1', 'bar' => 'Bar1');
+               $array2 = array('foo' => 'Foo2', 'bar' => 'Bar2');
+               $this->assertEquals($array1, $this->queryResult->current());
+               $this->assertTrue($this->queryResult->valid());
+               $this->queryResult->next();
+               $this->assertEquals($array2, $this->queryResult->current());
+               $this->assertTrue($this->queryResult->valid());
+               $this->assertEquals(1, $this->queryResult->key());
+               $this->queryResult->next();
+               $this->assertFalse($this->queryResult->current());
+               $this->assertFalse($this->queryResult->valid());
+               $this->assertNull($this->queryResult->key());
+               $this->queryResult->rewind();
+               $this->assertEquals(0, $this->queryResult->key());
+               $this->assertEquals($array1, $this->queryResult->current());
+       }
+
+       /**
+        * @test
+        */
+       public function initializeExecutesQueryWithArrayFetchMode() {
+               $queryResult = $this->getAccessibleMock('Tx_Extbase_Persistence_QueryResult', array('dummy'), array($this->query));
+               $queryResult->injectPersistenceManager($this->persistenceManager);
+               $queryResult->injectDataMapper($this->dataMapper);
+               $this->persistenceManager->expects($this->once())->method('getObjectDataByQuery')->with($this->query)->will($this->returnValue(array('FAKERESULT')));
+               $queryResult->_call('initialize');
+       }
+
+}
+?>
\ No newline at end of file
index 82b56ba..b7f8c35 100644 (file)
@@ -3,6 +3,7 @@
 *  Copyright notice
 *
 *  (c) 2009 Christopher Hlubek <hlubek@networkteam.com>
+*  (c) 2010 Bastian Waidelich <bastian@typo3.org>
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 ***************************************************************/
 
 class Tx_Extbase_Persistence_Query_testcase extends Tx_Extbase_BaseTestCase {
-       
+
+       /**
+        * @var Tx_Extbase_Persistence_Query
+        */
+       protected $query;
+
+       /**
+        * @var Tx_Extbase_Persistence_QuerySettingsInterface
+        */
+       protected $querySettings;
+
+       /**
+        * @var Tx_Extbase_Object_ObjectManagerInterface
+        */
+       protected $objectManager;
+
+       /**
+        * @var Tx_Extbase_Persistence_ManagerInterface
+        */
+       protected $persistenceManager;
+
+       /**
+        * @var Tx_Extbase_Persistence_BackendInterface
+        */
+       protected $backend;
+
+       /**
+        * @var Tx_Extbase_Persistence_DataMapper
+        */
+       protected $dataMapper;
+
+       /**
+        * Sets up this test case
+        * @return void
+        */
        public function setUp() {
+               $this->objectManager = $this->getMock('Tx_Extbase_Object_ObjectManagerInterface');
+               $this->query = new Tx_Extbase_Persistence_Query('someType');
+               $this->query->injectObjectManager($this->objectManager);
+               $this->querySettings = $this->getMock('Tx_Extbase_Persistence_QuerySettingsInterface');
+               $this->query->setQuerySettings($this->querySettings);
+               $this->persistenceManager = $this->getMock('Tx_Extbase_Persistence_ManagerInterface');
+               $this->backend = $this->getMock('Tx_Extbase_Persistence_BackendInterface');
+               $this->backend->expects($this->any())->method('getQomFactory')->will($this->returnValue(NULL));
+               $this->persistenceManager->expects($this->any())->method('getBackend')->will($this->returnValue($this->backend));
+               $this->query->injectPersistenceManager($this->persistenceManager);
+               $this->dataMapper = $this->getMock('Tx_Extbase_Persistence_Mapper_DataMapper');
+               $this->query->injectDataMapper($this->dataMapper);
+       }
+
+       /**
+        * @test
+        */
+       public function executeReturnsQueryResultInstanceAndInjectsItself() {
+               $queryResult = $this->getMock('Tx_Extbase_Persistence_QueryResult', array(), array(), '', FALSE);
+               $this->objectManager->expects($this->once())->method('create')->with('Tx_Extbase_Persistence_QueryResultInterface', $this->query)->will($this->returnValue($queryResult));
+               $actualResult = $this->query->execute();
+               $this->assertSame($queryResult, $actualResult);
+       }
+
+       /**
+        * @test
+        */
+       public function executeReturnsRawObjectDataIfRawQueryResultSettingIsTrue() {
+               $this->querySettings->expects($this->once())->method('getReturnRawQueryResult')->will($this->returnValue(TRUE));
+               $this->persistenceManager->expects($this->once())->method('getObjectDataByQuery')->with($this->query)->will($this->returnValue('rawQueryResult'));
+               $expectedResult = 'rawQueryResult';
+               $actualResult = $this->query->execute();
+               $this->assertEquals($expectedResult, $actualResult);
        }
-       
+
        /**
         * @test
         * @expectedException InvalidArgumentException
         */
        public function setLimitAcceptsOnlyIntegers() {
-               $query = new Tx_Extbase_Persistence_Query('Foo_Class_Name');
-               $query->setLimit(1.5);
+               $this->query->setLimit(1.5);
        }
 
        /**
@@ -41,8 +108,7 @@ class Tx_Extbase_Persistence_Query_testcase extends Tx_Extbase_BaseTestCase {
         * @expectedException InvalidArgumentException
         */
        public function setLimitRejectsIntegersLessThanOne() {
-               $query = new Tx_Extbase_Persistence_Query('Foo_Class_Name');
-               $query->setLimit(0);
+               $this->query->setLimit(0);
        }
 
        /**
@@ -50,8 +116,7 @@ class Tx_Extbase_Persistence_Query_testcase extends Tx_Extbase_BaseTestCase {
         * @expectedException InvalidArgumentException
         */
        public function setOffsetAcceptsOnlyIntegers() {
-               $query = new Tx_Extbase_Persistence_Query('Foo_Class_Name');
-               $query->setOffset(1.5);
+               $this->query->setOffset(1.5);
        }
 
        /**
@@ -59,9 +124,8 @@ class Tx_Extbase_Persistence_Query_testcase extends Tx_Extbase_BaseTestCase {
         * @expectedException InvalidArgumentException
         */
        public function setOffsetRejectsIntegersLessThanZero() {
-               $query = new Tx_Extbase_Persistence_Query('Foo_Class_Name');
-               $query->setOffset(-1);
+               $this->query->setOffset(-1);
        }
-       
+
 }
 ?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Tests/Persistence/Repository_testcase.php b/typo3/sysext/extbase/Tests/Persistence/Repository_testcase.php
new file mode 100644 (file)
index 0000000..5d74a85
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 Bastian Waidelich <bastian@typo3.org>
+*  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!
+***************************************************************/
+
+class Tx_Extbase_Persistence_Repository_testcase extends Tx_Extbase_BaseTestCase {
+
+       /**
+        * @var Tx_Extbase_Persistence_Repository
+        */
+       protected $repository;
+
+       /**
+        * @var Tx_Extbase_Persistence_IdentityMap
+        */
+       protected $identityMap;
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryFactory
+        */
+       protected $queryFactory;
+
+       /**
+        * @var Tx_Extbase_Persistence_ManagerInterface
+        */
+       protected $persistenceManager;
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryInterface
+        */
+       protected $query;
+
+       /**
+        * @var Tx_Extbase_Persistence_QuerySettingsInterface
+        */
+       protected $querySettings;
+
+       public function setUp() {
+               $this->identityMap = $this->getMock('Tx_Extbase_Persistence_IdentityMap');
+               $this->queryFactory = $this->getMock('Tx_Extbase_Persistence_QueryFactory');
+               $this->query = $this->getMock('Tx_Extbase_Persistence_QueryInterface');
+               $this->querySettings = $this->getMock('Tx_Extbase_Persistence_QuerySettingsInterface');
+               $this->query->expects($this->any())->method('getQuerySettings')->will($this->returnValue($this->querySettings));
+               $this->queryFactory->expects($this->any())->method('create')->will($this->returnValue($this->query));
+               $this->persistenceManager = $this->getMock('Tx_Extbase_Persistence_ManagerInterface');
+               $this->repository = $this->getAccessibleMock('Tx_Extbase_Persistence_Repository', array('dummy'), array($this->identityMap, $this->queryFactory, $this->persistenceManager));
+       }
+
+       /**
+        * @test
+        */
+       public function abstractRepositoryImplementsRepositoryInterface() {
+               $this->assertTrue($this->repository instanceof Tx_Extbase_Persistence_RepositoryInterface);
+       }
+
+       /**
+        * @test
+        */
+       public function addActuallyAddsAnObjectToTheInternalObjectsArray() {
+               $someObject = new stdClass();
+               $this->repository->_set('objectType', get_class($someObject));
+               $this->repository->add($someObject);
+
+               $this->assertTrue($this->repository->getAddedObjects()->contains($someObject));
+       }
+
+       /**
+        * @test
+        */
+       public function removeActuallyRemovesAnObjectFromTheInternalObjectsArray() {
+               $object1 = new stdClass();
+               $object2 = new stdClass();
+               $object3 = new stdClass();
+
+               $this->repository->_set('objectType', get_class($object1));
+               $this->repository->add($object1);
+               $this->repository->add($object2);
+               $this->repository->add($object3);
+
+               $this->repository->remove($object2);
+
+               $this->assertTrue($this->repository->getAddedObjects()->contains($object1));
+               $this->assertFalse($this->repository->getAddedObjects()->contains($object2));
+               $this->assertTrue($this->repository->getAddedObjects()->contains($object3));
+       }
+
+       /**
+        * @test
+        */
+       public function removeRemovesTheRightObjectEvenIfItHasBeenModifiedSinceItsAddition() {
+               $object1 = new ArrayObject(array('val' => '1'));
+               $object2 = new ArrayObject(array('val' => '2'));
+               $object3 = new ArrayObject(array('val' => '3'));
+
+               $this->repository->_set('objectType', get_class($object1));
+               $this->repository->add($object1);
+               $this->repository->add($object2);
+               $this->repository->add($object3);
+
+               $object2['foo'] = 'bar';
+               $object3['val'] = '2';
+
+               $this->repository->remove($object2);
+
+               $this->assertTrue($this->repository->getAddedObjects()->contains($object1));
+               $this->assertFalse($this->repository->getAddedObjects()->contains($object2));
+               $this->assertTrue($this->repository->getAddedObjects()->contains($object3));
+       }
+
+       /**
+        * Make sure we remember the objects that are not currently add()ed
+        * but might be in persistent storage.
+        *
+        * @test
+        */
+       public function removeRetainsObjectForObjectsNotInCurrentSession() {
+               $object = new ArrayObject(array('val' => '1'));
+               $this->repository->_set('objectType', get_class($object));
+               $this->repository->remove($object);
+
+               $this->assertTrue($this->repository->getRemovedObjects()->contains($object));
+       }
+
+       /**
+        * dataProvider for createQueryCallsQueryFactoryWithExpectedType
+        */
+       public function modelAndRepositoryClassNames() {
+               return array(
+                       array('Tx_BlogExample_Domain_Repository_BlogRepository', 'Tx_BlogExample_Domain_Model_Blog'),
+                       array('_Domain_Repository_Content_PageRepository', '_Domain_Model_Content_Page')
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider modelAndRepositoryClassNames
+        */
+       public function constructSetsObjectTypeFromClassName($repositoryClassName, $modelClassName) {
+               $mockClassName = 'MockRepository' . uniqid();
+               eval('class ' . $mockClassName . ' extends Tx_Extbase_Persistence_Repository {
+                       protected function getRepositoryClassName() {
+                               return \'' . $repositoryClassName . '\';
+                       }
+                       public function _getObjectType() {
+                               return $this->objectType;
+                       }
+               }');
+
+               $this->repository = new $mockClassName($this->identityMap, $this->queryFactory, $this->persistenceManager);
+               $this->assertEquals($modelClassName, $this->repository->_getObjectType());
+       }
+
+       /**
+        * @test
+        */
+       public function createQueryCallsQueryFactoryWithExpectedClassName() {
+               $this->queryFactory->expects($this->once())->method('create')->with('ExpectedType');
+               $this->repository->_set('objectType', 'ExpectedType');
+               $this->repository->createQuery();
+       }
+
+       /**
+        * @test
+        */
+       public function findAllCreatesQueryAndReturnsResultOfExecuteCall() {
+               $expectedResult = $this->getMock('Tx_Extbase_Persistence_QueryResultInterface');
+               $this->query->expects($this->once())->method('execute')->with()->will($this->returnValue($expectedResult));
+               $this->assertSame($expectedResult, $this->repository->findAll());
+       }
+
+       /**
+        * @test
+        */
+       public function findByUidReturnsResultOfGetObjectByIdentifierCall() {
+               $fakeUid = '123';
+               $object = new stdClass();
+
+               $this->persistenceManager->expects($this->once())->method('getObjectByIdentifier')->with($fakeUid)->will($this->returnValue($object));
+               $this->repository->_set('objectType', 'stdClass');
+
+               $this->assertSame($object, $this->repository->findByUid($fakeUid));
+       }
+
+       /**
+        * @test
+        */
+       public function findByUidReturnsNullIfObjectOfMismatchingTypeWasFoundByGetObjectByIdentifierCall() {
+               $fakeUUID = '123';
+               $object = new stdClass();
+
+               $this->persistenceManager->expects($this->once())->method('getObjectByIdentifier')->with($fakeUUID)->will($this->returnValue($object));
+               $this->repository->_set('objectType', 'otherExpectedClass');
+
+               $this->assertNULL($this->repository->findByUuid($fakeUUID));
+       }
+
+       /**
+        * Replacing a reconstituted object (which has a uuid) by a new object
+        * will ask the persistence backend to replace them accordingly in the
+        * identity map.
+        *
+        * @test
+        * @return void
+        */
+       public function replaceReconstitutedEntityByNewObject() {
+               $existingObject = new stdClass;
+               $newObject = new stdClass;
+
+               $this->persistenceManager->expects($this->once())->method('getIdentifierByObject')->with($existingObject)->will($this->returnValue('86ea8820-19f6-11de-8c30-0800200c9a66'));
+               $this->persistenceManager->expects($this->once())->method('replaceObject')->with($existingObject, $newObject);
+
+               $this->repository->_set('objectType', get_class($newObject));
+               $this->repository->replace($existingObject, $newObject);
+       }
+
+       /**
+        * Replacing a reconstituted object which during this session has been
+        * marked for removal (by calling the repository's remove method)
+        * additionally registers the "newObject" for removal and removes the
+        * "existingObject" from the list of removed objects.
+        *
+        * @test
+        * @return void
+        */
+       public function replaceReconstitutedObjectWhichIsMarkedToBeRemoved() {
+               $existingObject = new stdClass;
+               $newObject = new stdClass;
+
+               $removedObjects = new SplObjectStorage;
+               $removedObjects->attach($existingObject);
+
+               $this->persistenceManager->expects($this->once())->method('getIdentifierByObject')->with($existingObject)->will($this->returnValue('86ea8820-19f6-11de-8c30-0800200c9a66'));
+               $this->persistenceManager->expects($this->once())->method('replaceObject')->with($existingObject, $newObject);
+
+               $this->repository->_set('objectType', get_class($newObject));
+               $this->repository->_set('removedObjects', $removedObjects);
+               $this->repository->replace($existingObject, $newObject);
+
+               $this->assertFalse($removedObjects->contains($existingObject));
+               $this->assertTrue($removedObjects->contains($newObject));
+       }
+
+       /**
+        * Replacing a new object which has not yet been persisted by another
+        * new object will just replace them in the repository's list of added
+        * objects.
+        *
+        * @test
+        * @return void
+        */
+       public function replaceNewObjectByNewObject() {
+               $existingObject = new stdClass;
+               $newObject = new stdClass;
+
+               $addedObjects = new SplObjectStorage;
+               $addedObjects->attach($existingObject);
+
+               $this->persistenceManager->expects($this->once())->method('getIdentifierByObject')->with($existingObject)->will($this->returnValue(NULL));
+               $this->persistenceManager->expects($this->never())->method('replaceObject');
+
+               $this->repository->_set('objectType', get_class($newObject));
+               $this->repository->_set('addedObjects', $addedObjects);
+               $this->repository->replace($existingObject, $newObject);
+
+               $this->assertFalse($addedObjects->contains($existingObject));
+               $this->assertTrue($addedObjects->contains($newObject));
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_IllegalObjectType
+        */
+       public function replaceChecksObjectType() {
+               $this->repository->_set('objectType', 'ExpectedObjectType');
+
+               $this->repository->replace(new stdClass(), new stdClass());
+       }
+
+       /**
+        * @test
+        */
+       public function updateReplacesAnObjectWithTheSameUuidByTheGivenObject() {
+               $existingObject = new stdClass;
+               $modifiedObject = $this->getMock('FooBar' . uniqid(), array('FLOW3_Persistence_isClone'));
+               $modifiedObject->expects($this->once())->method('FLOW3_Persistence_isClone')->will($this->returnValue(TRUE));
+
+               $this->persistenceManager->expects($this->once())->method('getIdentifierByObject')->with($modifiedObject)->will($this->returnValue('86ea8820-19f6-11de-8c30-0800200c9a66'));
+               $this->persistenceManager->expects($this->once())->method('getObjectByIdentifier')->with('86ea8820-19f6-11de-8c30-0800200c9a66')->will($this->returnValue($existingObject));
+
+               $this->repository->expects($this->once())->method('replaceObject')->with($existingObject, $modifiedObject);
+
+               $this->repository->_set('objectType', get_class($modifiedObject));
+               $this->repository->update($modifiedObject);
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_IllegalObjectType
+        */
+       public function updateRejectsNonClonedObjects() {
+               $someObject = $this->getMock('FooBar' . uniqid(), array('FLOW3_Persistence_isClone'));
+               $someObject->expects($this->once())->method('FLOW3_Persistence_isClone')->will($this->returnValue(FALSE));
+
+               $this->repository->_set('objectType', get_class($someObject));
+
+               $this->repository->update($someObject);
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_IllegalObjectType
+        */
+       public function updateRejectsObjectsOfWrongType() {
+               $this->repository->_set('objectType', 'Foo');
+               $this->repository->update(new stdClass());
+       }
+
+       /**
+        * @test
+        */
+       public function magicCallMethodAcceptsFindBySomethingCallsAndExecutesAQueryWithThatCriteria() {
+               $mockQueryResult = $this->getMock('Tx_Extbase_Persistence_QueryResultInterface');
+               $mockQuery = $this->getMock('Tx_Extbase_Persistence_QueryInterface');
+               $mockQuery->expects($this->once())->method('equals')->with('foo', 'bar')->will($this->returnValue('matchCriteria'));
+               $mockQuery->expects($this->once())->method('matching')->with('matchCriteria')->will($this->returnValue($mockQuery));
+               $mockQuery->expects($this->once())->method('execute')->with()->will($this->returnValue($mockQueryResult));
+
+               $this->repository->expects($this->once())->method('createQuery')->will($this->returnValue($mockQuery));
+
+               $this->assertSame($mockQueryResult, $this->repository->findByFoo('bar'));
+       }
+
+       /**
+        * @test
+        */
+       public function magicCallMethodAcceptsFindOneBySomethingCallsAndExecutesAQueryWithThatCriteria() {
+               $object = new stdClass();
+               $mockQueryResult = $this->getMock('Tx_Extbase_Persistence_QueryResultInterface');
+               $mockQueryResult->expects($this->once())->method('getFirst')->will($this->returnValue($object));
+               $mockQuery = $this->getMock('Tx_Extbase_Persistence_QueryInterface');
+               $mockQuery->expects($this->once())->method('equals')->with('foo', 'bar')->will($this->returnValue('matchCriteria'));
+               $mockQuery->expects($this->once())->method('matching')->with('matchCriteria')->will($this->returnValue($mockQuery));
+               $mockQuery->expects($this->once())->method('execute')->will($this->returnValue($mockQueryResult));
+
+               $this->repository->expects($this->once())->method('createQuery')->will($this->returnValue($mockQuery));
+
+               $this->assertSame($object, $this->repository->findOneByFoo('bar'));
+       }
+
+       /**
+        * @test
+        */
+       public function magicCallMethodAcceptsCountBySomethingCallsAndExecutesAQueryWithThatCriteria() {
+               $mockQueryResult = $this->getMock('Tx_Extbase_Persistence_QueryResultInterface');
+               $mockQueryResult->expects($this->once())->method('count')->will($this->returnValue(2));
+               $mockQuery = $this->getMock('Tx_Extbase_Persistence_QueryInterface');
+               $mockQuery->expects($this->once())->method('equals')->with('foo', 'bar')->will($this->returnValue('matchCriteria'));
+               $mockQuery->expects($this->once())->method('matching')->with('matchCriteria')->will($this->returnValue($mockQuery));
+               $mockQuery->expects($this->once())->method('execute')->will($this->returnValue($mockQueryResult));
+
+               $this->repository->expects($this->once())->method('createQuery')->will($this->returnValue($mockQuery));
+
+               $this->assertSame(2, $this->repository->countByFoo('bar'));
+       }
+
+       /**
+        * @test
+        * @expectedException Exception
+        */
+       public function magicCallMethodTriggersAnErrorIfUnknownMethodsAreCalled() {
+               $this->repository->__call('foo', array());
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_IllegalObjectType
+        */
+       public function addChecksObjectType() {
+               $this->repository->_set('objectType', 'ExpectedObjectType');
+
+               $this->repository->add(new stdClass());
+       }
+
+       /**
+        * @test
+        * @expectedException Tx_Extbase_Persistence_Exception_IllegalObjectType
+        */
+       public function removeChecksObjectType() {
+               $this->repository->_set('objectType', 'ExpectedObjectType');
+
+               $this->repository->remove(new stdClass());
+       }
+
+}
+?>
\ No newline at end of file