diff options
-rw-r--r-- | .gitattributes | 2 | ||||
-rw-r--r-- | HISTORY | 7 | ||||
-rw-r--r-- | framework/Caching/TCache.php | 439 | ||||
-rw-r--r-- | framework/Exceptions/messages.txt | 3 | ||||
-rw-r--r-- | framework/interfaces.php | 12 | ||||
-rw-r--r-- | tests/unit/Caching/TDirectoryCacheDependencyTest.php | 84 | ||||
-rw-r--r-- | tests/unit/Caching/TFileCacheDependencyTest.php | 60 |
7 files changed, 598 insertions, 9 deletions
diff --git a/.gitattributes b/.gitattributes index 751393fa..b2dcbe05 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2338,6 +2338,8 @@ tests/test_tools/simpletest/reflection_php5.php -text tests/test_tools/simpletest/selector.php -text tests/test_tools/simpletest/simpletest.php -text tests/test_tools/simpletest/test_case.php -text +tests/unit/Caching/TDirectoryCacheDependencyTest.php -text +tests/unit/Caching/TFileCacheDependencyTest.php -text tests/unit/Collections/TListTest.php -text tests/unit/Collections/TMapTest.php -text tests/unit/Data/TDbCommandTest.php -text @@ -1,9 +1,11 @@ Version 3.1.0 To be released ============================ BUG: Ticket#188 - Postback js caused controls not inheritable (Qiang) -ENH: Ticket#180, #222 - Prado js files are not rendered at the beginning of the form (Qiang) ENH: Ticket#99 - TXmlTransform control (Knut) ENH: Ticket#117 - added support to configure subproperties in a group. (Qiang) +ENH: Ticket#180, #222 - Prado js files are not rendered at the beginning of the form (Qiang) +CHG: All validators ClientSide.OnSuccess becomes ClientSide.OnValidationSuccess, and OnSuccess event becomes OnValidationSuccess. (Wei) +CHG: All validators ClientSide.OnError becomes ClientSide.OnValidationError, and OnError event becomes OnValidationError. (Wei) NEW: Ajax support and active controls (Wei) NEW: Data abstraction layer based on PDO (Qiang) NEW: SQLMap (Wei) @@ -12,8 +14,7 @@ NEW: TQueue (Qiang) NEW: TSessionPageStatePersister (Qiang) NEW: TFeedService, TRssFeedDocument (Knut, Qiang) NEW: TJsonService -CHG: All validators ClientSide.OnSuccess becomes ClientSide.OnValidationSuccess, and OnSuccess event becomes OnValidationSuccess. (Wei) -CHG: All validators ClientSide.OnError becomes ClientSide.OnValidationError, and OnError event becomes OnValidationError. (Wei) +NEW: TCacheDependency, TFileCacheDependency, TDirectoryCacheDependency, TGlobalStateCacheDependency, TChainedCacheDependency Version 3.0.6 December 4, 2006 ============================== diff --git a/framework/Caching/TCache.php b/framework/Caching/TCache.php index d974fe83..ca7348d4 100644 --- a/framework/Caching/TCache.php +++ b/framework/Caching/TCache.php @@ -1,6 +1,6 @@ <?php
/**
- * TCache class file.
+ * TCache and cache dependency classes.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.pradosoft.com/
@@ -10,6 +10,8 @@ * @package System.Caching
*/
+Prado::using('System.Collections.TList');
+
/**
* TCache class
*
@@ -229,4 +231,439 @@ abstract class TCache extends TModule implements ICache abstract protected function deleteValue($key);
}
+
+/**
+ * TCacheDependency class.
+ *
+ * TCacheDependency is the base class implementing {@link ICacheDependency} interface.
+ * Descendant classes must implement {@link checkChanges()} to provide
+ * actual dependency checking logic.
+ *
+ * The property value of {@link getHasChanged HasChanged} tells whether
+ * the dependency is changed or not.
+ *
+ * You may disable the dependency checking by setting {@link setEnabled Enabled}
+ * to false.
+ *
+ * Note, since the dependency objects often need to be serialized so that
+ * they can persist across requests, you may need to implement __sleep() and
+ * __wakeup() if the dependency objects contain resource handles which are
+ * not serializable.
+ *
+ * Currently, the following dependency classes are provided in the PRADO release:
+ * - {@link TFileCacheDependency}: checks whether a file is changed or not
+ * - {@link TDirectoryCacheDependency}: checks whether a directory is changed or not
+ * - {@link TGlobalStateCacheDependency}: checks whether a global state is changed or not
+ * - {@link TChainedCacheDependency}: checks whether any of a list of dependencies is changed or not
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+abstract class TCacheDependency extends TComponent implements ICacheDependency
+{
+ private $_enabled=true;
+
+ /**
+ * @return boolean whether this dependency is enabled. Default value is true.
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * Sets a value indicating whether this cache dependency is enabled or not.
+ * Cache dependency checking is only performed when it is enabled.
+ * @param boolean whether this dependency is to be enabled.
+ */
+ public function setEnabled($value)
+ {
+ $this->_enabled=TPropertyValue::ensureBoolean($value);
+ }
+
+ /**
+ * @return boolean whether the dependency is changed or not.
+ * If the dependency checking is disabled, it always returns false.
+ */
+ public function getHasChanged()
+ {
+ return $this->_enabled ? $this->checkChanges() : false;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method must be implemented by child classes.
+ * @return boolean whether the dependency is changed or not.
+ */
+ abstract protected function checkChanges();
+}
+
+
+/**
+ * TFileCacheDependency class.
+ *
+ * TFileCacheDependency performs dependency checking based on the
+ * last modification time of the file specified via {@link setFileName FileName}.
+ * The dependency is reported as unchanged if and only if the file's
+ * last modification time remains unchanged.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TFileCacheDependency extends TCacheDependency
+{
+ private $_fileName;
+ private $_timestamp;
+
+ /**
+ * Constructor.
+ * @param string name of the file whose change is to be checked.
+ */
+ public function __construct($fileName)
+ {
+ $this->setFileName($fileName);
+ }
+
+ /**
+ * @return string the name of the file whose change is to be checked
+ */
+ public function getFileName()
+ {
+ return $this->_fileName;
+ }
+
+ /**
+ * @param string the name of the file whose change is to be checked
+ */
+ public function setFileName($value)
+ {
+ $this->_fileName=$value;
+ $this->_timestamp=@filemtime($value);
+ }
+
+ /**
+ * @return int the last modification time of the file
+ */
+ public function getTimestamp()
+ {
+ return $this->_timestamp;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the last modification time of the file is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ protected function checkChanges()
+ {
+ return @filemtime($this->_fileName)!==$this->_timestamp;
+ }
+}
+
+/**
+ * TDirectoryCacheDependency class.
+ *
+ * TDirectoryCacheDependency performs dependency checking based on the
+ * modification time of the files contained in the specified directory.
+ * The directory being checked is specified via {@link setDirectory Directory}.
+ *
+ * By default, all files under the specified directory and subdirectories
+ * will be checked. If the last modification time of any of them is changed
+ * or if different number of files are contained in a directory, the dependency
+ * is reported as changed. By specifying {@link setRecursiveCheck RecursiveCheck}
+ * and {@link setRecursiveLevel RecursiveLevel}, one can limit the checking
+ * to a certain depth of the subdirectories.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TDirectoryCacheDependency extends TCacheDependency
+{
+ private $_recursiveCheck=true;
+ private $_recursiveLevel=-1;
+ private $_timestamps;
+ private $_directory;
+
+ /**
+ * Constructor.
+ * @param string the directory to be checked
+ */
+ public function __construct($directory)
+ {
+ $this->setDirectory($directory);
+ }
+
+ /**
+ * @return string the directory to be checked
+ */
+ public function getDirectory()
+ {
+ return $this->_directory;
+ }
+
+ /**
+ * @param string the directory to be checked
+ * @throws TInvalidDataValueException if the directory does not exist
+ */
+ public function setDirectory($directory)
+ {
+ if(($path=realpath($directory))===false || !is_dir($path))
+ throw new TInvalidDataValueException('directorycachedependency_directory_invalid',$directory);
+ $this->_directory=$path;
+ $this->_timestamps=$this->generateTimestamps($path);
+ }
+
+ /**
+ * @return boolean whether the subdirectories of the directory will also be checked.
+ * It defaults to true.
+ */
+ public function getRecursiveCheck()
+ {
+ return $this->_recursiveCheck;
+ }
+
+ /**
+ * @param boolean whether the subdirectories of the directory will also be checked.
+ */
+ public function setRecursiveCheck($value)
+ {
+ $this->_recursiveCheck=TPropertyValue::ensureBoolean($value);
+ }
+
+ /**
+ * @return int the depth of the subdirectories to be checked.
+ * It defaults to -1, meaning unlimited depth.
+ */
+ public function getRecursiveLevel()
+ {
+ return $this->_recursiveLevel;
+ }
+
+ /**
+ * Sets a value indicating the depth of the subdirectories to be checked.
+ * This is meaningful only when {@link getRecursiveCheck RecursiveCheck}
+ * is true.
+ * @param int the depth of the subdirectories to be checked.
+ * If the value is less than 0, it means unlimited depth.
+ * If the value is 0, it means checking the files directly under the specified directory.
+ */
+ public function setRecursiveLevel($value)
+ {
+ $this->_recursiveLevel=TPropertyValue::ensureInteger($value);
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the directory is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ protected function checkChanges()
+ {
+ return $this->generateTimestamps($this->_directory)!=$this->_timestamps;
+ }
+
+ /**
+ * Checks to see if the file should be checked for dependency.
+ * This method is invoked when dependency of the whole directory is being checked.
+ * By default, it always returns true, meaning the file should be checked.
+ * You may override this method to check only certain files.
+ * @param string the name of the file that may be checked for dependency.
+ * @return boolean whether this file should be checked.
+ */
+ protected function validateFile($fileName)
+ {
+ return true;
+ }
+
+ /**
+ * Checks to see if the specified subdirectory should be checked for dependency.
+ * This method is invoked when dependency of the whole directory is being checked.
+ * By default, it always returns true, meaning the subdirectory should be checked.
+ * You may override this method to check only certain subdirectories.
+ * @param string the name of the subdirectory that may be checked for dependency.
+ * @return boolean whether this subdirectory should be checked.
+ */
+ protected function validateDirectory($directory)
+ {
+ return true;
+ }
+
+ /**
+ * Determines the last modification time for files under the directory.
+ * This method may go recurisvely into subdirectories if
+ * {@link setRecursiveCheck RecursiveCheck} is set true.
+ * @param string the directory name
+ * @param int level of the recursion
+ * @return array list of file modification time indexed by the file path
+ */
+ protected function generateTimestamps($directory,$level=0)
+ {
+ if(($dir=opendir($directory))===false)
+ throw new TIOException('directorycachedependency_directory_invalid',$directory);
+ $timestamps=array();
+ while(($file=readdir($dir))!==false)
+ {
+ $path=$directory.DIRECTORY_SEPARATOR.$file;
+ if($file==='.' || $file==='..')
+ continue;
+ else if(is_dir($path))
+ {
+ if(($this->_recursiveLevel<0 || $level<$this->_recursiveLevel) && $this->validateDirectory($path))
+ $timestamps=array_merge($this->generateTimestamps($path,$level+1));
+ }
+ else if($this->validateFile($path))
+ $timestamps[$path]=filemtime($path);
+ }
+ closedir($dir);
+ return $timestamps;
+ }
+}
+
+
+/**
+ * TGlobalStateCacheDependency class.
+ *
+ * TGlobalStateCacheDependency checks if a global state is changed or not.
+ * If the global state is changed, the dependency is reported as changed.
+ * To specify which global state this dependency should check with,
+ * set {@link setStateName StateName} to the name of the global state.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TGlobalStateCacheDependency extends TCacheDependency
+{
+ private $_stateName;
+ private $_stateValue;
+
+ /**
+ * Constructor.
+ * @param string the name of the global state
+ */
+ public function __construct($name)
+ {
+ $this->setStateName($name);
+ }
+
+ /**
+ * @return string the name of the global state
+ */
+ public function getStateName()
+ {
+ return $this->_stateName;
+ }
+
+ /**
+ * @param string the name of the global state
+ * @see TApplication::setGlobalState
+ */
+ public function setStateName($value)
+ {
+ $this->_stateName=$value;
+ $this->_stateValue=Prado::getApplication()->getGlobalState($value);
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if the specified global state is changed.
+ * @return boolean whether the dependency is changed or not.
+ */
+ protected function checkChanges()
+ {
+ return $this->_stateValue!==Prado::getApplication()->getGlobalState($value);
+ }
+}
+
+
+/**
+ * TChainedCacheDependency class.
+ *
+ * TChainedCacheDependency represents a list of cache dependency objects
+ * and performs the dependency checking based on the checking results of
+ * these objects. If any of them reports a dependency change, TChainedCacheDependency
+ * will return true for the checking.
+ *
+ * To add dependencies to TChainedCacheDependency, use {@link getDependencies Dependencies}
+ * which gives a {@link TCacheDependencyList} instance and can be used like an array
+ * (see {@link TList} for more details}).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TChainedCacheDependency extends TCacheDependency
+{
+ private $_dependencies=null;
+
+ /**
+ * @return TCacheDependencyList list of dependency objects
+ */
+ public function getDependencies()
+ {
+ if($this->_dependencies===null)
+ $this->_dependencies=new TCacheDependencyList;
+ return $this->_dependencies;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if any of the dependency objects
+ * reports a dependency change.
+ * @return boolean whether the dependency is changed or not.
+ */
+ protected function checkChanges()
+ {
+ if($this->_dependencies!==null)
+ {
+ foreach($this->_dependencies as $dependency)
+ if($dependency->getHasChanged())
+ return true;
+ }
+ return false;
+ }
+}
+
+
+/**
+ * TCacheDependencyList class.
+ *
+ * TCacheDependencyList represents a list of cache dependency objects.
+ * Only objects implementing {@link ICacheDependency} can be added into this list.
+ *
+ * TCacheDependencyList can be used like an array. See {@link TList}
+ * for more details.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id $
+ * @package System.Caching
+ * @since 3.1.0
+ */
+class TCacheDependencyList extends TList
+{
+ /**
+ * Inserts an item at the specified position.
+ * This overrides the parent implementation by performing additional type checking
+ * for each newly added item.
+ * @param integer the speicified position.
+ * @param mixed new item
+ * @throws TInvalidDataTypeException if the item to be inserted is not a dependency instance
+ */
+ public function insertAt($index,$item)
+ {
+ if($item instanceof ICacheDependency)
+ parent::insertAt($index,$item);
+ else
+ throw new TInvalidDataTypeException('cachedependencylist_cachedependency_required');
+ }
+}
+
?>
\ No newline at end of file diff --git a/framework/Exceptions/messages.txt b/framework/Exceptions/messages.txt index dcd3c749..d176b5f7 100644 --- a/framework/Exceptions/messages.txt +++ b/framework/Exceptions/messages.txt @@ -363,3 +363,6 @@ dbcommand_query_failed = TDbCommand failed to execute the query SQL: {0} dbcommand_column_empty = TDbCommand returned an empty result and could not obtain the scalar.
dbdatareader_rewind_invalid = TDbDataReader is a forward-only stream. It can only be traversed once.
dbtransaction_transaction_inactive = TDbTransaction is inactive.
+
+directorycachedependency_directory_invalid = TDirectoryCacheDependency.Directory {0} does not refer to a valid directory.
+cachedependencylist_cachedependency_required = Only objects implementing ICacheDependency can be added into TCacheDependencyList.
\ No newline at end of file diff --git a/framework/interfaces.php b/framework/interfaces.php index e5da7bcd..d0e5c6aa 100644 --- a/framework/interfaces.php +++ b/framework/interfaces.php @@ -229,6 +229,8 @@ interface ICache * This interface must be implemented by classes meant to be used as
* cache dependencies.
*
+ * Classes implementing this interface must support serialization and unserialization.
+ *
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package System
@@ -282,9 +284,9 @@ interface IBindable /**
* IActiveControl interface.
- *
+ *
* Active controls must implement IActiveControl interface.
- *
+ *
* @author Wei Zhuo <weizhuo[at]gamil[dot]com>
* @version $Id$
* @package System
@@ -295,15 +297,15 @@ interface IActiveControl /**
* @return TBaseActiveControl Active control properties.
*/
- public function getActiveControl();
+ public function getActiveControl();
}
/**
* ICallbackEventHandler interface.
- *
+ *
* If a control wants to respond to callback event, it must implement this
* interface.
- *
+ *
* @author Wei Zhuo <weizhuo[at]gamil[dot]com>
* @version $Id$
* @package System
diff --git a/tests/unit/Caching/TDirectoryCacheDependencyTest.php b/tests/unit/Caching/TDirectoryCacheDependencyTest.php new file mode 100644 index 00000000..786720da --- /dev/null +++ b/tests/unit/Caching/TDirectoryCacheDependencyTest.php @@ -0,0 +1,84 @@ +<?php + +require_once(dirname(__FILE__).'/../phpunit2.php'); + +/** + * @package System.Caching + */ +class TDirectoryCacheDependencyTest extends PHPUnit2_Framework_TestCase +{ + public function setUp() + { + } + + public function tearDown() + { + } + + public function testDirectoryName() + { + $directory=realpath(dirname(__FILE__).'/temp'); + $dependency=new TDirectoryCacheDependency(dirname(__FILE__).'/temp'); + $this->assertEquals($dependency->getDirectory(),$directory); + + try + { + $dependency=new TDirectoryCacheDependency(dirname(__FILE__).'/temp2'); + $this->fail("Expected exception is not raised"); + } + catch(TInvalidDataValueException $e) + { + } + } + + public function testRecursiveCheck() + { + $directory=realpath(dirname(__FILE__).'/temp'); + $dependency=new TDirectoryCacheDependency(dirname(__FILE__).'/temp'); + $this->assertTrue($dependency->getRecursiveCheck()); + $dependency->setRecursiveCheck(false); + $this->assertFalse($dependency->getRecursiveCheck()); + } + + public function testRecursiveLevel() + { + $directory=realpath(dirname(__FILE__).'/temp'); + $dependency=new TDirectoryCacheDependency(dirname(__FILE__).'/temp'); + $this->assertEquals($dependency->getRecursiveLevel(),-1); + $dependency->setRecursiveLevel(5); + $this->assertEquals($dependency->getRecursiveLevel(),5); + } + + public function testHasChanged() + { + $tempFile=dirname(__FILE__).'/temp/foo.txt'; + @unlink($tempFile); + $fw=fopen($tempFile,"w"); + fwrite($fw,"test"); + fclose($fw); + clearstatcache(); + + $dependency=new TDirectoryCacheDependency(dirname($tempFile)); + $str=serialize($dependency); + + // test directory not changed + sleep(2); + $dependency=unserialize($str); + $this->assertFalse($dependency->getHasChanged()); + + // change file + $fw=fopen($tempFile,"w"); + fwrite($fw,"test again"); + fclose($fw); + clearstatcache(); + + // test file changed + sleep(2); + $dependency=unserialize($str); + $this->assertTrue($dependency->getHasChanged()); + + @unlink($tempFile); + } +} + +?>
\ No newline at end of file diff --git a/tests/unit/Caching/TFileCacheDependencyTest.php b/tests/unit/Caching/TFileCacheDependencyTest.php new file mode 100644 index 00000000..6b61aed6 --- /dev/null +++ b/tests/unit/Caching/TFileCacheDependencyTest.php @@ -0,0 +1,60 @@ +<?php + +require_once(dirname(__FILE__).'/../phpunit2.php'); + +/** + * @package System.Caching + */ +class TFileCacheDependencyTest extends PHPUnit2_Framework_TestCase +{ + public function setUp() + { + } + + public function tearDown() + { + } + + public function testFileName() + { + $dependency=new TFileCacheDependency(__FILE__); + $this->assertEquals($dependency->getFileName(),__FILE__); + $this->assertEquals($dependency->getTimestamp(),filemtime(__FILE__)); + + $dependency=new TFileCacheDependency(dirname(__FILE__).'/foo.txt'); + $this->assertFalse($dependency->getTimestamp()); + } + + public function testHasChanged() + { + $tempFile=dirname(__FILE__).'/temp/foo.txt'; + @unlink($tempFile); + $fw=fopen($tempFile,"w"); + fwrite($fw,"test"); + fclose($fw); + clearstatcache(); + + $dependency=new TFileCacheDependency($tempFile); + $str=serialize($dependency); + + // test file not changed + sleep(2); + $dependency=unserialize($str); + $this->assertFalse($dependency->getHasChanged()); + + // change file + $fw=fopen($tempFile,"w"); + fwrite($fw,"test again"); + fclose($fw); + clearstatcache(); + + // test file changed + sleep(2); + $dependency=unserialize($str); + $this->assertTrue($dependency->getHasChanged()); + + @unlink($tempFile); + } +} + +?>
\ No newline at end of file |