Added feature #11438: Add a registry to t3lib
authorIngo Renner <ingo.renner@typo3.org>
Fri, 14 Aug 2009 08:53:11 +0000 (08:53 +0000)
committerIngo Renner <ingo.renner@typo3.org>
Fri, 14 Aug 2009 08:53:11 +0000 (08:53 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@5783 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_registry.php [new file with mode: 0644]
t3lib/core_autoload.php
t3lib/stddb/tables.sql
tests/t3lib/t3lib_registry_testcase.php [new file with mode: 0644]

index b1e316f..dc1c862 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2009-08-14  Ingo Renner  <ingo@typo3.org>
+
+       * Added feature #11438: Add a registry to t3lib
+
 2009-08-14  Michael Stucki  <michael@typo3.org>
 
        * Fixed bug #8968: DBAL incompatible SQL in "impexp" extension (thanks to Marc Bastian Heinrichs)
diff --git a/t3lib/class.t3lib_registry.php b/t3lib/class.t3lib_registry.php
new file mode 100644 (file)
index 0000000..595c642
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Ingo Renner <ingo@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!
+***************************************************************/
+
+
+/**
+ * A class to store and retrieve entries in a registry database table.
+ *
+ * The intention is to have a place where we can store things (mainly settings)
+ * that should live for more than one request, longer than a session, and that
+ * shouldn't expire like it would with a cache. You can actually think of it
+ * being like the Windows Registry in some ways.
+ *
+ * Credits: Heavily inspired by Drupal's variable_*() functions.
+ *
+ * @author     Ingo Renner <ingo@typo3.org>
+ * @author     Bastian Waidelich <bastian@typo3.org>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_Registry implements t3lib_Singleton {
+
+       /**
+        * @var array
+        */
+       protected $entries = array();
+
+
+       /**
+        * Returns a persistent entry.
+        *
+        * @param       string  Extension key for extensions starting with 'tx_' / 'user_' or 'core' for core registry entries
+        * @param       string  The key of the entry to return.
+        * @param       mixed   Optional default value to use if this entry has never been set. Defaults to null.
+        * @return      mixed   The value of the entry.
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       public function get($namespace, $key, $defaultValue = null) {
+               if (!isset($this->entries[$namespace])) {
+                       $this->loadEntriesByNamespace($namespace);
+               }
+
+               return isset($this->entries[$namespace][$key]) ? $this->entries[$namespace][$key] : $defaultValue;
+       }
+
+       /**
+        * Sets a persistent entry.
+        * Do not store binary data into the registry, it's not build to do that,
+        * instead use the proper way to store binary data: The filesystem.
+        *
+        * @param       string  Extension key for extensions starting with 'tx_' / 'user_' or 'core' for core registry entries.
+        * @param       string  The key of the entry to set.
+        * @param       mixed   The value to set. This can be any PHP data type; this class takes care of serialization if necessary.
+        * @return      void
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       public function set($namespace, $key, $value) {
+               $this->validateNamespace($namespace);
+               $serializedValue = serialize($value);
+
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                       'uid',
+                       'sys_registry',
+                       'entry_namespace = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($namespace, 'sys_registry')
+                       . ' AND entry_key = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($key, 'sys_registry')
+               );
+               if ($GLOBALS['TYPO3_DB']->sql_num_rows($res) < 1) {
+                       $GLOBALS['TYPO3_DB']->exec_INSERTquery(
+                               'sys_registry',
+                               array(
+                                       'entry_namespace' => $namespace,
+                                       'entry_key'       => $key,
+                                       'entry_value'     => $serializedValue
+                               )
+                       );
+               } else {
+                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
+                               'sys_registry',
+                               'entry_namespace = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($namespace, 'sys_registry')
+                               . ' AND entry_key = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($key, 'sys_registry'),
+                               array(
+                                       'entry_value' => $serializedValue
+                               )
+                       );
+               }
+
+               $this->entries[$namespace][$key] = $value;
+       }
+
+       /**
+        * Unsets a persistent entry.
+        *
+        * @param       string  Namespace. extension key for extensions or 'core' for core registry entries
+        * @param       string  The key of the entry to unset.
+        * @return      void
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       public function remove($namespace, $key) {
+               $this->validateNamespace($namespace);
+
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(
+                       'sys_registry',
+                       'entry_namespace = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($namespace, 'sys_registry')
+                       . ' AND entry_key = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($key, 'sys_registry')
+               );
+
+               unset($this->entries[$namespace][$key]);
+       }
+
+       /**
+        * Unsets all persistent entries of the given namespace.
+        *
+        * @param       string  Namespace. extension key for extensions or 'core' for core registry entries
+        * @return      void
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       public function removeAllByNamespace($namespace) {
+               $this->validateNamespace($namespace);
+
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(
+                       'sys_registry',
+                       'entry_namespace = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($namespace, 'sys_registry')
+               );
+
+               unset($this->entries[$namespace]);
+       }
+
+       /**
+        * Loads all entries of the given namespace into the internal $entries cache.
+        *
+        * @param       string  Namespace. extension key for extensions or 'core' for core registry entries
+        * @return      void
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       protected function loadEntriesByNamespace($namespace) {
+               $this->validateNamespace($namespace);
+
+               $storedEntries = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       '*',
+                       'sys_registry',
+                       'entry_namespace = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($namespace, 'sys_registry')
+               );
+
+               foreach ($storedEntries as $storedEntry) {
+                       $key = $storedEntry['entry_key'];
+                       $this->entries[$namespace][$key] = unserialize($storedEntry['entry_value']);
+               }
+       }
+
+       /**
+        * Checks the given namespace. If it does not have a valid format an
+        * exception is thrown.
+        * Allowed namespaces are 'core', 'tx_*' and 'user_*'
+        *
+        * @param       string  Namespace. extension key for extensions or 'core' for core registry entries
+        * @return      void
+        * @throws      InvalidArgumentException        Throws an exception if the given namespace is not valid
+        */
+       protected function validateNamespace($namespace) {
+               if (t3lib_div::isFirstPartOfStr($namespace, 'tx_') || t3lib_div::isFirstPartOfStr($namespace, 'user_')) {
+                       return;
+               }
+
+               if ($namespace !== 'core') {
+                       throw new InvalidArgumentException(
+                               '"' . $namespace . '" is no valid Namespace. The namespace has to be prefixed with "tx_", "user_" or must be equal to "core"',
+                               1249755131
+                       );
+               }
+       }
+
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_registry.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_registry.php']);
+}
+
+?>
\ No newline at end of file
index 082395c..a6bcba2 100644 (file)
@@ -47,6 +47,7 @@ return array(
        't3lib_readmail' => PATH_t3lib . 'class.t3lib_readmail.php',
        't3lib_recordlist' => PATH_t3lib . 'class.t3lib_recordlist.php',
        't3lib_refindex' => PATH_t3lib . 'class.t3lib_refindex.php',
+       't3lib_registry' => PATH_t3lib . 'class.t3lib_registry.php',
        't3lib_rteapi' => PATH_t3lib . 'class.t3lib_rteapi.php',
        't3lib_scbase' => PATH_t3lib . 'class.t3lib_scbase.php',
        't3lib_softrefproc' => PATH_t3lib . 'class.t3lib_softrefproc.php',
index 138ab04..b381037 100644 (file)
@@ -188,6 +188,18 @@ CREATE TABLE pages (
 );
 
 #
+# Table structure for table 'sys_registry'
+#
+CREATE TABLE sys_registry (
+  uid int(11) unsigned NOT NULL auto_increment,
+  entry_namespace varchar(128) DEFAULT '' NOT NULL,
+  entry_key varchar(255) DEFAULT '' NOT NULL,
+  entry_value blob,
+  PRIMARY KEY (uid),
+  UNIQUE KEY entry_identifier (entry_namespace,entry_key)
+);
+
+#
 # Table structure for table 'sys_be_shortcuts'
 #
 CREATE TABLE sys_be_shortcuts (
diff --git a/tests/t3lib/t3lib_registry_testcase.php b/tests/t3lib/t3lib_registry_testcase.php
new file mode 100644 (file)
index 0000000..2290d60
--- /dev/null
@@ -0,0 +1,356 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 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!
+***************************************************************/
+
+
+/**
+ * Testcase for class t3lib_Registry
+ *
+ * @author     Bastian Waidelich <bastian@typo3.org>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_Registry_testcase extends tx_phpunit_testcase {
+
+       /**
+        * @var t3lib_Registry
+        */
+       protected $registry;
+
+       /**
+        * @var t3lib_DB
+        */
+       protected $typo3DbBackup;
+
+       /**
+        * Sets up this testcase
+        */
+       public function setUp() {
+               $this->typo3DbBackup = $GLOBALS['TYPO3_DB'];
+               $GLOBALS['TYPO3_DB'] = $this->getMock('t3lib_DB', array());
+               $GLOBALS['TYPO3_DB']->expects($this->any())
+                       ->method('fullQuoteStr')
+                       ->will($this->onConsecutiveCalls('\'tx_phpunit\'', '\'someKey\'', '\'tx_phpunit\'', '\'someKey\''));
+
+               $this->registry = new t3lib_Registry();
+       }
+
+       /**
+        * Tears down this testcase
+        */
+       public function tearDown() {
+               $GLOBALS['TYPO3_DB'] = $this->typo3DbBackup;
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function getThrowsExceptionForInvalidNamespaces() {
+               $this->registry->get('invalidNamespace', 'someKey');
+       }
+
+       /**
+        * @test
+        */
+       public function getRetrievesTheCorrectEntry() {
+               $testKey = 't3lib_Registry_testcase.testData.getRetrievesTheCorrectEntry';
+               $testValue = 'getRetrievesTheCorrectEntry';
+
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->with('*', 'sys_registry', 'entry_namespace = \'tx_phpunit\'')
+                       ->will(
+                               $this->returnValue(
+                                       array(
+                                               array('entry_key' => $testKey, 'entry_value' => serialize($testValue))
+                                       )
+                               )
+                       );
+
+               $this->assertEquals(
+                       $this->registry->get('tx_phpunit', $testKey),
+                       $testValue,
+                       'The actual data did not match the expected data.'
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getLazyLoadsEntriesOfOneNamespace() {
+               $testKey1 = 't3lib_Registry_testcase.testData.getLazyLoadsEntriesOfOneNamespace1';
+               $testValue1 = 'getLazyLoadsEntriesOfOneNamespace1';
+               $testKey2 = 't3lib_Registry_testcase.testData.getLazyLoadsEntriesOfOneNamespace2';
+               $testValue2 = 'getLazyLoadsEntriesOfOneNamespace2';
+
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->with('*', 'sys_registry', 'entry_namespace = \'tx_phpunit\'')
+                       ->will(
+                               $this->returnValue(
+                                       array(
+                                               array('entry_key' => $testKey1, 'entry_value' => serialize($testValue1)),
+                                               array('entry_key' => $testKey2, 'entry_value' => serialize($testValue2))
+                                       )
+                               )
+                       );
+
+               $this->assertEquals(
+                       $this->registry->get('tx_phpunit', $testKey1),
+                       $testValue1,
+                       'The actual data did not match the expected data.'
+               );
+               $this->assertEquals(
+                       $this->registry->get('tx_phpunit', $testKey2),
+                       $testValue2,
+                       'The actual data did not match the expected data.'
+               );
+       }
+
+       /**
+        * @test
+        */
+       public function getReturnsTheDefaultValueIfTheRequestedKeyWasNotFound() {
+               $defaultValue = 'getReturnsTheDefaultValueIfTheRequestedKeyWasNotFound';
+
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_SELECTgetRows')
+                       ->with('*', 'sys_registry', 'entry_namespace = \'tx_phpunit\'')
+                       ->will(
+                               $this->returnValue(
+                                       array(
+                                               array('entry_key' => 'foo', 'entry_value' => 'bar'),
+                                       )
+                               )
+                       );
+
+               $this->assertEquals(
+                       $defaultValue,
+                       $this->registry->get('tx_phpunit', 'someNonExistingKey', $defaultValue),
+                       'A value other than the default value was returned.'
+               );
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function setThrowsAnExceptionOnEmptyNamespace() {
+               $this->registry->set('', 'someKey', 'someValue');
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function setThrowsAnExceptionOnWrongNamespace() {
+               $this->registry->set('invalidNamespace', 'someKey', 'someValue');
+       }
+
+       /**
+        * @test
+        */
+       public function setAllowsValidNamespaces() {
+               $this->registry->set('tx_thisIsValid', 'someKey', 'someValue');
+               $this->registry->set('user_soIsThis', 'someKey', 'someValue');
+               $this->registry->set('core', 'someKey', 'someValue');
+       }
+
+       /**
+        * @test
+        */
+       public function setReallySavesTheGivenValueToTheDatabase() {
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_INSERTquery')
+                       ->with(
+                               'sys_registry',
+                               array(
+                                       'entry_namespace' => 'tx_phpunit',
+                                       'entry_key' => 'someKey',
+                                       'entry_value' => serialize('someValue')
+                               )
+                       );
+
+               $this->registry->set('tx_phpunit', 'someKey', 'someValue');
+       }
+
+       /**
+        * @test
+        */
+       public function setUpdatesExistingKeys() {
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_SELECTquery')
+                       ->with(
+                               'uid',
+                               'sys_registry',
+                               'entry_namespace = \'tx_phpunit\' AND entry_key = \'someKey\''
+                       )
+                       ->will(
+                               $this->returnValue('DBResource')
+                       );
+
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('sql_num_rows')
+                       ->with('DBResource')
+                       ->will(
+                               $this->returnValue(1)
+                       );
+
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_UPDATEquery')
+                       ->with(
+                               'sys_registry',
+                               'entry_namespace = \'tx_phpunit\' AND entry_key = \'someKey\'',
+                               array(
+                                       'entry_value' => serialize('someValue')
+                               )
+                       );
+
+               $GLOBALS['TYPO3_DB']->expects($this->never())
+                       ->method('exec_INSERTquery');
+
+               $this->registry->set('tx_phpunit', 'someKey', 'someValue');
+       }
+
+       /**
+        * @test
+        */
+       public function setStoresValueInTheInternalEntriesCache() {
+               $registry = $this->getMock('t3lib_Registry', array('loadEntriesByNamespace'));
+               $registry->expects($this->never())
+                       ->method('loadEntriesByNamespace');
+
+               $registry->set('tx_phpunit', 'someKey', 'someValue');
+
+               $this->assertEquals(
+                       'someValue',
+                       $registry->get('tx_phpunit', 'someKey'),
+                       'The actual data did not match the expected data.'
+               );
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function removeThrowsAnExceptionOnWrongNamespace() {
+               $this->registry->remove('coreInvalid', 'someKey');
+       }
+
+       /**
+        * @test
+        */
+       public function removeReallyRemovesTheEntryFromTheDatabase() {
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_DELETEquery')
+                       ->with(
+                               'sys_registry',
+                               'entry_namespace = \'tx_phpunit\' AND entry_key = \'someKey\''
+                       );
+
+               $this->registry->remove('tx_phpunit', 'someKey');
+       }
+
+       /**
+        * @test
+        */
+       public function removeUnsetsValueFromTheInternalEntriesCache() {
+               $registry = $this->getMock('t3lib_Registry', array('loadEntriesByNamespace'));
+               $registry->set('tx_phpunit', 'someKey', 'someValue');
+               $registry->set('tx_phpunit', 'someOtherKey', 'someOtherValue');
+               $registry->set('tx_otherNamespace', 'someKey', 'someValueInOtherNamespace');
+               $registry->remove('tx_phpunit', 'someKey');
+
+               $this->assertEquals(
+                       'defaultValue',
+                       $registry->get('tx_phpunit', 'someKey', 'defaultValue'),
+                       'A value other than the default value was returned, thus the entry was still present.'
+               );
+
+               $this->assertEquals(
+                       'someOtherValue',
+                       $registry->get('tx_phpunit', 'someOtherKey', 'defaultValue'),
+                       'A value other than the stored value was returned, thus the entry was removed.'
+               );
+
+               $this->assertEquals(
+                       'someValueInOtherNamespace',
+                       $registry->get('tx_otherNamespace', 'someKey', 'defaultValue'),
+                       'A value other than the stored value was returned, thus the entry was removed.'
+               );
+       }
+
+       /**
+        * @test
+        * @expectedException InvalidArgumentException
+        */
+       public function removeAllByNamespaceThrowsAnExceptionOnWrongNamespace() {
+               $this->registry->removeAllByNamespace('');
+       }
+
+       /**
+        * @test
+        */
+       public function removeAllByNamespaceReallyRemovesAllEntriesOfTheSpecifiedNamespaceFromTheDatabase() {
+               $GLOBALS['TYPO3_DB']->expects($this->once())
+                       ->method('exec_DELETEquery')
+                       ->with(
+                               'sys_registry',
+                               'entry_namespace = \'tx_phpunit\''
+                       );
+
+               $this->registry->removeAllByNamespace('tx_phpunit');
+       }
+
+       /**
+        * @test
+        */
+       public function removeAllByNamespaceUnsetsValuesOfTheSpecifiedNamespaceFromTheInternalEntriesCache() {
+               $registry = $this->getMock('t3lib_Registry', array('loadEntriesByNamespace'));
+               $registry->set('tx_phpunit', 'someKey', 'someValue');
+               $registry->set('tx_phpunit', 'someOtherKey', 'someOtherValue');
+               $registry->set('tx_otherNamespace', 'someKey', 'someValueInOtherNamespace');
+               $registry->removeAllByNamespace('tx_phpunit');
+
+               $this->assertEquals(
+                       'defaultValue',
+                       $registry->get('tx_phpunit', 'someKey', 'defaultValue'),
+                       'A value other than the default value was returned, thus the entry was still present.'
+               );
+
+               $this->assertEquals(
+                       'defaultValue',
+                       $registry->get('tx_phpunit', 'someOtherKey', 'defaultValue'),
+                       'A value other than the default value was returned, thus the entry was still present.'
+               );
+
+               $this->assertEquals(
+                       'someValueInOtherNamespace',
+                       $registry->get('tx_otherNamespace', 'someKey', 'defaultValue'),
+                       'A value other than the stored value was returned, thus the entry was removed.'
+               );
+       }
+}
+
+?>
\ No newline at end of file