summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes2
-rw-r--r--HISTORY7
-rw-r--r--framework/Caching/TCache.php439
-rw-r--r--framework/Exceptions/messages.txt3
-rw-r--r--framework/interfaces.php12
-rw-r--r--tests/unit/Caching/TDirectoryCacheDependencyTest.php84
-rw-r--r--tests/unit/Caching/TFileCacheDependencyTest.php60
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
diff --git a/HISTORY b/HISTORY
index 7c197661..57727471 100644
--- a/HISTORY
+++ b/HISTORY
@@ -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