Index: t3lib/stddb/tables.sql =================================================================== --- t3lib/stddb/tables.sql (revision 5779) +++ t3lib/stddb/tables.sql (working copy) @@ -188,6 +188,18 @@ ); # +# 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 ( Index: t3lib/core_autoload.php =================================================================== --- t3lib/core_autoload.php (revision 5779) +++ t3lib/core_autoload.php (working copy) @@ -47,6 +48,7 @@ '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: t3lib/class.t3lib_registry.php =================================================================== --- t3lib/class.t3lib_registry.php (revision 0) +++ t3lib/class.t3lib_registry.php (revision 0) @@ -0,0 +1,214 @@ + +* +* 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 + * @author Bastian Waidelich + * @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 + * @author Ingo Renner + * @author Bastian Waidelich + */ + public function get($namespace, $key, $defaultValue = null) { + $this->validateNamespace($namespace); + + 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 + * @author Ingo Renner + * @author Bastian Waidelich + */ + 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 + * @author Ingo Renner + * @author Bastian Waidelich + */ + 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 + * @author Bastian Waidelich + */ + 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 + * @author Bastian Waidelich + */ + 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 + * @author Bastian Waidelich + */ + 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: tests/t3lib/t3lib_registry_testcase.php =================================================================== --- tests/t3lib/t3lib_registry_testcase.php (revision 0) +++ tests/t3lib/t3lib_registry_testcase.php (revision 0) @@ -0,0 +1,356 @@ + +* 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 + * @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