[FEATURE] Extend collection handling by repository
authorSteffen Ritter <info@rs-websystems.de>
Wed, 8 Feb 2012 15:34:37 +0000 (16:34 +0100)
committerOliver Hader <oliver@typo3.org>
Fri, 10 Feb 2012 09:32:25 +0000 (10:32 +0100)
The table sys_collection allows to group several objects and
persist them in one collection object. The data access shall
happen by a repository, which is integrated with this change
set.

Change-Id: Ib3401f2e3bd4c267f92aef59dbc36756b54cce0a
Resolves: #33755
Releases: 4.7
Reviewed-on: http://review.typo3.org/8913
Reviewed-by: Georg Ringer
Reviewed-by: Stefan Neufeind
Reviewed-by: Oliver Hader
Tested-by: Oliver Hader
t3lib/collection/AbstractRecordCollection.php
t3lib/collection/RecordCollectionRepository.php [new file with mode: 0644]
t3lib/collection/StaticRecordCollection.php
t3lib/core_autoload.php
t3lib/stddb/tables.sql
t3lib/stddb/tbl_be.php
tests/t3lib/collection/RecordCollectionRepositoryTest.php [new file with mode: 0644]

index 4f3d16c..7e150a0 100644 (file)
@@ -44,7 +44,6 @@
  * @subpackage t3lib
  */
 abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collection_RecordCollection, t3lib_collection_Persistable, t3lib_collection_Sortable {
-
        /**
         * The table name collections are stored to
         *
@@ -87,7 +86,6 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
         */
        protected $itemTableName;
 
-
        /**
         * The local storage
         *
@@ -95,6 +93,9 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
         */
        protected $storage;
 
+       /**
+        * Creates this object.
+        */
        public function __construct() {
                $this->storage = new SplDoublyLinkedList();
        }
@@ -238,7 +239,6 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
                $this->description = $desc;
        }
 
-
        /**
         * Setter for the name of the data-source table
         *
@@ -317,12 +317,11 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
         * Entries can be load on first access.
         *
         * @static
-        * @param int|string $id
+        * @param integer $id Id of database record to be loaded
         * @param boolean $fillItems Populates the entries directly on load, might be bad for memory on large collections
         * @return t3lib_collection_Collection
         */
        public static function load($id, $fillItems = FALSE) {
-               $collection = new static();
                t3lib_div::loadTCA(static::$storageTableName);
 
                $collectionRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
@@ -331,6 +330,20 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
                        'uid=' . intval($id) . t3lib_BEfunc::deleteClause(static::$storageTableName)
                );
 
+               return self::create($collectionRecord, $fillItems);
+       }
+
+       /**
+        * Creates a new collection objects and reconstitutes the
+        * given database record to the new object.
+        *
+        * @static
+        * @param array $collectionRecord Database record
+        * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections
+        * @return t3lib_collection_Collection
+        */
+       public static function create(array $collectionRecord, $fillItems = FALSE) {
+               $collection = new static();
                $collection->fromArray($collectionRecord);
 
                if ($fillItems) {
@@ -423,7 +436,5 @@ abstract class t3lib_collection_AbstractRecordCollection implements t3lib_collec
                $this->description      = $array['description'];
                $this->itemTableName= $array['table_name'];
        }
-
 }
-
 ?>
\ No newline at end of file
diff --git a/t3lib/collection/RecordCollectionRepository.php b/t3lib/collection/RecordCollectionRepository.php
new file mode 100644 (file)
index 0000000..e49407a
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2011-2012 Steffen Ritter <typo3@steffen-ritter.net>
+ * 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.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * 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!
+ ***************************************************************/
+
+/**
+ * Implements the repository for record collections.
+ *
+ * @author Steffen Ritter <typo3@steffen-ritter.net>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_collection_RecordCollectionRepository {
+       const TYPE_Static = 'static';
+
+       /**
+        * Name of the table the collection records are stored to
+        *
+        * @var string
+        */
+       protected $table = 'sys_collection';
+
+       /**
+        * @var string
+        */
+       protected $typeField = 'type';
+
+       /**
+        * @var string
+        */
+       protected $tableField = 'table_name';
+
+       /**
+        * Finds a record collection by uid.
+        *
+        * @param integer $uid The uid to be looked up
+        * @return NULL|t3lib_collection_RecordCollection
+        */
+       public function findByUid($uid) {
+               $result = NULL;
+
+               $data = $this->getDatabase()->exec_SELECTgetSingleRow(
+                       'uid',
+                       $this->table,
+                       'uid=' . intval($uid) . t3lib_BEfunc::deleteClause($this->table)
+               );
+
+               if ($data !== NULL) {
+                       $result = $this->createDomainObject($data);
+               }
+
+               return $result;
+       }
+
+       /**
+        * Finds record collections by table name.
+        *
+        * @param string $tableName Name of the table to be looked up
+        * @return t3lib_collection_RecordCollection[]
+        */
+       public function findByTableName($tableName) {
+               $conditions = array(
+                       $this->tableField . '=' . $this->getDatabase()->fullQuoteStr($tableName, $this->table),
+               );
+
+               return $this->queryMultipleRecords($conditions);
+       }
+
+       /**
+        * Finds record collection by type.
+        *
+        * @param string $type Type to be looked up
+        * @return NULL|t3lib_collection_RecordCollection[]
+        */
+       public function findByType($type) {
+               $conditions = array(
+                       $this->typeField . '=' . $this->getDatabase()->fullQuoteStr($type, $this->table),
+               );
+
+               return $this->queryMultipleRecords($conditions);
+       }
+
+       /**
+        * Finds record collections by type and table name.
+        *
+        * @param string $type Type to be looked up
+        * @param string $tableName Name of the table to be looked up
+        * @return NULL|t3lib_collection_RecordCollection[]
+        */
+       public function findByTypeAndTableName($type, $tableName) {
+               $conditions = array(
+                       $this->typeField . '=' . $this->getDatabase()->fullQuoteStr($type, $this->table),
+                       $this->tableField . '=' . $this->getDatabase()->fullQuoteStr($tableName, $this->table),
+               );
+
+               return $this->queryMultipleRecords($conditions);
+       }
+
+       /**
+        * Deletes a record collection by uid.
+        *
+        * @param integer $uid uid to be deleted
+        */
+       public function deleteByUid($uid) {
+               $this->getDatabase()->exec_UPDATEquery(
+                       $this->table, 'uid=' . intval($uid),
+                       array('deleted' => 1, 'tstamp' => $GLOBALS['EXEC_TIME'])
+               );
+       }
+
+       /**
+        * Queries for multiple records for the given conditions.
+        *
+        * @param array $conditions Conditions concatenated with AND for query
+        * @return NULL|t3lib_collection_RecordCollection[]
+        */
+       protected function queryMultipleRecords(array $conditions = array()) {
+               $result = NULL;
+
+               $data = $this->getDatabase()->exec_SELECTgetRows(
+                       '*',
+                       $this->table,
+                       implode(' AND ', $conditions) . t3lib_BEfunc::deleteClause($this->table)
+               );
+
+               if ($data !== NULL) {
+                       $result = $this->createMultipleDomainObjects($data);
+               }
+
+               return $result;
+       }
+
+       /**
+        * Creates a record collection domain object.
+        *
+        * @param array $record Database record to be reconstituted
+        * @return t3lib_collection_RecordCollection
+        */
+       protected function createDomainObject(array $record) {
+               switch ($record['type']) {
+                       case self::TYPE_Static:
+                               $collection = t3lib_collection_StaticRecordCollection::create($record);
+                               break;
+                       default:
+                               throw new RuntimeException('Unknown record collection type "' . $record['type'], 1328646798);
+               }
+
+               return $collection;
+       }
+
+       /**
+        * Creates multiple record collection domain objects.
+        *
+        * @param array $data Array of multiple database records to be reconstituted
+        * @return t3lib_collection_RecordCollection[]
+        */
+       protected function createMultipleDomainObjects(array $data) {
+               $collections = array();
+
+               foreach ($data as $collection) {
+                       $collections[] = $this->createDomainObject($collection);
+               }
+
+               return $collections;
+       }
+
+       /**
+        * Gets the database object.
+        *
+        * @return t3lib_DB
+        */
+       protected function getDatabase() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+}
+?>
\ No newline at end of file
index 79d9205..8f84da0 100644 (file)
 
 
 /**
- * implementation of a RecordCollection for static TCA-Records
+ * Implementation of a RecordCollection for static TCA-Records
  *
  * @author Steffen Ritter <typo3@steffen-ritter.net>
  * @package TYPO3
  * @subpackage t3lib
  */
 class t3lib_collection_StaticRecordCollection extends t3lib_collection_AbstractRecordCollection implements t3lib_collection_Editable {
-
        /**
         * Populates the content-entries of the storage
         *
@@ -128,8 +127,5 @@ class t3lib_collection_StaticRecordCollection extends t3lib_collection_AbstractR
        public function removeAll() {
                $this->storage = new SplDoublyLinkedList();
        }
-
-
 }
-
 ?>
\ No newline at end of file
index 1d81c04..4f491ec 100644 (file)
@@ -54,6 +54,7 @@ $t3libClasses = array(
        't3lib_collection_nameable' => PATH_t3lib . 'collection/interfaces/interface.t3lib_collection_nameable.php',
        't3lib_collection_persistable' => PATH_t3lib . 'collection/interfaces/interface.t3lib_collection_persistable.php',
        't3lib_collection_recordcollection' => PATH_t3lib . 'collection/interfaces/interface.t3lib_collection_recordcollection.php',
+       't3lib_collection_recordcollectionrepository' => PATH_t3lib . 'collection/RecordCollectionRepository.php',
        't3lib_collection_sortable' => PATH_t3lib . 'collection/interfaces/interface.t3lib_collection_sortable.php',
        't3lib_collection_staticrecordcollection' => PATH_t3lib . 'collection/StaticRecordCollection.php',
        't3lib_compressor' => PATH_t3lib . 'class.t3lib_compressor.php',
@@ -255,4 +256,4 @@ $t3libClasses = array(
 $tslibClasses = require(PATH_typo3 . 'sysext/cms/ext_autoload.php');
 
 return array_merge($t3libClasses, $tslibClasses);
-?>
\ No newline at end of file
+?>
index 8c97d9c..a0fc1c6 100644 (file)
@@ -277,7 +277,7 @@ CREATE TABLE sys_collection (
 
        title tinytext,
        description text,
-       type varchar(6) DEFAULT 'static' NOT NULL,
+       type varchar(32) DEFAULT 'static' NOT NULL,
        table_name tinytext,
        items int(11) DEFAULT '0' NOT NULL,
        criteria text NOT NULL,
@@ -288,10 +288,9 @@ CREATE TABLE sys_collection (
 );
 
 #
-# mm-table for entries of sys_collection
+# Table structure for table 'sys_collection_entries'
 #
 CREATE TABLE sys_collection_entries (
-
        uid int(11) NOT NULL auto_increment,
        uid_local int(11) DEFAULT '0' NOT NULL,
        uid_foreign int(11) DEFAULT '0' NOT NULL,
@@ -300,7 +299,7 @@ CREATE TABLE sys_collection_entries (
 
        KEY uid_local (uid_local),
        KEY uid_foreign (uid_foreign),
-       PRIMARY KEY (uid),
+       PRIMARY KEY (uid)
 );
 
 #
index 426b554..304ebc1 100644 (file)
@@ -705,7 +705,7 @@ $TCA['sys_filemounts'] = array(
  */
 $TCA['sys_collection'] = array(
        'ctrl' => $TCA['sys_collection']['ctrl'],
-       'interface' => array (
+       'interface' => array(
                'showRecordFieldList' => 'title, description, table_name, items'
        ),
        'feInterface' => $TCA['sys_collection']['feInterface'],
@@ -731,7 +731,7 @@ $TCA['sys_collection'] = array(
                                )
                        )
                ),
-               'l10n_parent' => array (
+               'l10n_parent' => array(
                        'displayCond' => 'FIELD:sys_language_uid:>:0',
                        'exclude'     => 1,
                        'label'       => 'LLL:EXT:lang/locallang_general.xml:LGL.l18n_parent',
@@ -745,14 +745,14 @@ $TCA['sys_collection'] = array(
                        )
                ),
                'l10n_diffsource' => array(
-                       'config' => array (
+                       'config' => array(
                                'type' => 'passthrough'
                        )
                ),
                'hidden' => array(
                        'exclude' => 1,
                        'label'   => 'LLL:EXT:lang/locallang_general.xml:LGL.hidden',
-                       'config'  => array (
+                       'config'  => array(
                                'type'    => 'check',
                                'default' => '0'
                        )
@@ -760,7 +760,7 @@ $TCA['sys_collection'] = array(
                'starttime' => array(
                        'exclude' => 1,
                        'label'   => 'LLL:EXT:lang/locallang_general.xml:LGL.starttime',
-                       'config'  => array (
+                       'config'  => array(
                                'type'     => 'input',
                                'size'     => '8',
                                'max'      => '20',
@@ -772,7 +772,7 @@ $TCA['sys_collection'] = array(
                'endtime' => array(
                        'exclude' => 1,
                        'label'   => 'LLL:EXT:lang/locallang_general.xml:LGL.endtime',
-                       'config'  => array (
+                       'config'  => array(
                                'type'     => 'input',
                                'size'     => '8',
                                'max'      => '20',
@@ -802,7 +802,7 @@ $TCA['sys_collection'] = array(
                'table_name' => array(
                        'exclude' => 0,
                        'label' => 'LLL:EXT:lang/locallang_tca.xlf:sys_collection.table_name',
-                       'config' => array (
+                       'config' => array(
                                'type' => 'select',
                                'special' => 'tables'
                        )
@@ -818,7 +818,6 @@ $TCA['sys_collection'] = array(
                                'MM' => 'sys_collection_entries',
                                'MM_hasUidField' => TRUE,
                                'multiple' => TRUE,
-
                        )
                ),
                'title' => array(
@@ -846,7 +845,6 @@ $TCA['sys_collection'] = array(
                                'type' => 'select',
                                'items' => array(
                                        array('LLL:EXT:lang/locallang_tca.xlf:sys_collection.type.static', 'static'),
-                                       array('LLL:EXT:lang/locallang_tca.xlf:sys_collection.type.filter', 'filter')
                                ),
                                'default' => 'static'
                        )
@@ -859,11 +857,11 @@ $TCA['sys_collection'] = array(
                        )
                )
        ),
-       'types' => array (
+       'types' => array(
                'static' => array('showitem' => 'title;;1,type, description,table_name, items'),
                'filter' => array('showitem' => 'title;;1,type, description,table_name')
        ),
-       'palettes' => array (
+       'palettes' => array(
                '1' => array('showitem' => 'starttime, endtime, fe_group, sys_language_uid, l10n_parent, l10n_diffsource, hidden')
        )
 );
diff --git a/tests/t3lib/collection/RecordCollectionRepositoryTest.php b/tests/t3lib/collection/RecordCollectionRepositoryTest.php
new file mode 100644 (file)
index 0000000..b12ca83
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2012 Oliver Hader <oliver.hader@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.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * 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!
+ ***************************************************************/
+
+/**
+ * Test case for t3lib_collection_RecordCollectionRepository
+ *
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_collection_RecordCollectionRepositoryTest extends Tx_Phpunit_TestCase {
+       /**
+        * @var t3lib_collection_RecordCollectionRepository
+        */
+       protected $fixture;
+
+       /**
+        * @var t3lib_DB
+        */
+       protected $databaseMock;
+
+       protected function setUp() {
+               $this->databaseMock = $this->getMock(
+                       't3lib_DB',
+                       array('exec_UPDATEquery', 'exec_SELECTgetSingleRow', 'exec_SELECTgetRows')
+               );
+
+               $this->fixture = $this->getMock(
+                       't3lib_collection_RecordCollectionRepository',
+                       array('getDatabase')
+               );
+               $this->fixture
+                       ->expects($this->any())
+                       ->method('getDatabase')
+                       ->will($this->returnValue($this->databaseMock));
+       }
+
+       protected function tearDown() {
+               unset($this->databaseMock);
+               unset($this->fixture);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByUidReturnNull() {
+               $testUid = rand(1, 1000);
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetSingleRow')
+                       ->will($this->returnValue(NULL));
+
+               $object = $this->fixture->findByUid($testUid);
+               $this->assertNull($object);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByUidReturnObject() {
+               $testUid = rand(1, 1000);
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetSingleRow')
+                       ->will($this->returnValue(
+                               array('uid' => $testUid, 'type' => t3lib_collection_RecordCollectionRepository::TYPE_Static)
+                       ));
+
+               $object = $this->fixture->findByUid($testUid);
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $object);
+       }
+
+       /**
+        * @test
+        * @expectedException RuntimeException
+        */
+       public function doesFindByUidThrowException() {
+               $testUid = rand(1, 1000);
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetSingleRow')
+                       ->will($this->returnValue(
+                               array('uid' => $testUid, 'type' => uniqid('unknown'))
+                       ));
+
+               $object = $this->fixture->findByUid($testUid);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTypeReturnNull() {
+               $type = t3lib_collection_RecordCollectionRepository::TYPE_Static;
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(NULL));
+
+               $objects = $this->fixture->findByType($type);
+
+               $this->assertNull($objects);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTypeReturnObjects() {
+               $testUid = rand(1, 1000);
+               $type = t3lib_collection_RecordCollectionRepository::TYPE_Static;
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(
+                               array(
+                                       array('uid' => $testUid, 'type' => $type),
+                                       array('uid' => $testUid, 'type' => $type),
+                               )
+                       ));
+
+               $objects = $this->fixture->findByType($type);
+
+               $this->assertEquals(2, count($objects));
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[0]);
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[1]);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTableNameReturnNull() {
+               $testTable = uniqid('sys_collection_');
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(NULL));
+
+               $objects = $this->fixture->findByTableName($testTable);
+
+               $this->assertNull($objects);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTableNameReturnObjects() {
+               $testUid = rand(1, 1000);
+               $testTable = uniqid('sys_collection_');
+               $type = t3lib_collection_RecordCollectionRepository::TYPE_Static;
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(
+                               array(
+                                       array('uid' => $testUid, 'type' => $type),
+                                       array('uid' => $testUid, 'type' => $type),
+                               )
+                       ));
+
+               $objects = $this->fixture->findByTableName($testTable);
+
+               $this->assertEquals(2, count($objects));
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[0]);
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[1]);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTypeAndTableNameReturnNull() {
+               $testTable = uniqid('sys_collection_');
+               $type = t3lib_collection_RecordCollectionRepository::TYPE_Static;
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(NULL));
+
+               $objects = $this->fixture->findByTypeAndTableName($type, $testTable);
+
+               $this->assertNull($objects);
+       }
+
+       /**
+        * @test
+        */
+       public function doesFindByTypeAndTableNameReturnObjects() {
+               $testUid = rand(1, 1000);
+               $testTable = uniqid('sys_collection_');
+               $type = t3lib_collection_RecordCollectionRepository::TYPE_Static;
+
+               $this->databaseMock
+                       ->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->will($this->returnValue(
+                               array(
+                                       array('uid' => $testUid, 'type' => $type),
+                                       array('uid' => $testUid, 'type' => $type),
+                               )
+                       ));
+
+               $objects = $this->fixture->findByTypeAndTableName($type, $testTable);
+
+               $this->assertEquals(2, count($objects));
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[0]);
+               $this->assertInstanceOf('t3lib_collection_StaticRecordCollection', $objects[1]);
+       }
+}
+?>
\ No newline at end of file