summaryrefslogtreecommitdiff
path: root/framework/Web/UI/TCachePageStatePersister.php
blob: 62c2bc605b93ed98b5c953f67909dd707af1b375 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<?php
/**
 * TCachePageStatePersister class file
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2011 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id$
 * @package System.Web.UI
 */

/**
 * TCachePageStatePersister class
 *
 * TCachePageStatePersister implements a page state persistent method based on cache.
 * Page state are stored in cache (e.g. memcache, DB cache, etc.), and only a small token
 * is passed to the client side to identify the state. This greatly reduces the size of
 * the page state that needs to be transmitted between the server and the client. Of course,
 * this is at the cost of using server side resource.
 *
 * A cache module has to be loaded in order to use TCachePageStatePersister.
 * By default, TCachePageStatePersister will use the primary cache module.
 * A non-primary cache module can be used by setting {@link setCacheModuleID CacheModuleID}.
 * Any cache module, as long as it implements the interface {@link ICache}, may be used.
 * For example, one can use {@link TDbCache}, {@link TMemCache}, {@link TAPCCache}, etc.
 *
 * TCachePageStatePersister uses {@link setCacheTimeout CacheTimeout} to limit the data
 * that stores in cache.
 *
 * Since server resource is often limited, be cautious if you plan to use TCachePageStatePersister
 * for high-traffic Web pages. You may consider using a small {@link setCacheTimeout CacheTimeout}.
 *
 * There are a couple of ways to use TCachePageStatePersister.
 * One can override the page's {@link TPage::getStatePersister()} method and
 * create a TCachePageStatePersister instance there.
 * Or one can configure the pages to use TCachePageStatePersister in page configurations
 * as follows,
 * <code>
 *   <pages StatePersisterClass="System.Web.UI.TCachePageStatePersister"
 *          StatePersister.CacheModuleID="mycache"
 *          StatePersister.CacheTimeout="3600" />
 * </code>
 * Note in the above, we use StatePersister.CacheModuleID to configure the cache module ID
 * for the TCachePageStatePersister instance.
 *
 * The above configuration will affect the pages under the directory containing
 * this configuration and all its subdirectories.
 * To configure individual pages to use TCachePageStatePersister, use
 * <code>
 *   <pages>
 *     <page id="PageID" StatePersisterClass="System.Web.UI.TCachePageStatePersister" />
 *   </pages>
 * </code>
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @version $Id$
 * @package System.Web.UI
 * @since 3.1.1
 */
class TCachePageStatePersister extends TComponent implements IPageStatePersister
{
	private $_prefix='statepersister';
	private $_page;
	private $_cache=null;
	private $_cacheModuleID='';
	private $_timeout=1800;

	/**
	 * @param TPage the page that this persister works for
	 */
	public function getPage()
	{
		return $this->_page;
	}

	/**
	 * @param TPage the page that this persister works for.
	 */
	public function setPage(TPage $page)
	{
		$this->_page=$page;
	}

	/**
	 * @return string the ID of the cache module.
	 */
	public function getCacheModuleID()
	{
		return $this->_cacheModuleID;
	}

	/**
	 * @param string the ID of the cache module. If not set, the primary cache module will be used.
	 */
	public function setCacheModuleID($value)
	{
		$this->_cacheModuleID=$value;
	}

	/**
	 * @return ICache the cache module being used for data storage
	 */
	public function getCache()
	{
		if($this->_cache===null)
		{
			if($this->_cacheModuleID!=='')
				$cache=Prado::getApplication()->getModule($this->_cacheModuleID);
			else
				$cache=Prado::getApplication()->getCache();
			if($cache===null || !($cache instanceof ICache))
			{
				if($this->_cacheModuleID!=='')
					throw new TConfigurationException('cachepagestatepersister_cachemoduleid_invalid',$this->_cacheModuleID);
				else
					throw new TConfigurationException('cachepagestatepersister_cache_required');
			}
			$this->_cache=$cache;
		}
		return $this->_cache;
	}

	/**
	 * @return integer the number of seconds in which the cached state will expire. Defaults to 1800.
	 */
	public function getCacheTimeout()
	{
		return $this->_timeout;
	}

	/**
	 * @param integer the number of seconds in which the cached state will expire. 0 means never expire.
	 * @throws TInvalidDataValueException if the number is smaller than 0.
	 */
	public function setCacheTimeout($value)
	{
		if(($value=TPropertyValue::ensureInteger($value))>=0)
			$this->_timeout=$value;
		else
			throw new TInvalidDataValueException('cachepagestatepersister_timeout_invalid');
	}

	/**
	 * @return string prefix of cache variable name to avoid conflict with other cache data. Defaults to 'statepersister'.
	 */
	public function getKeyPrefix()
	{
	    return $this->_prefix;
	}

	/**
     * @param string prefix of cache variable name to avoid conflict with other cache data
     */
	public function setKeyPrefix($value)
	{
	    $this->_prefix=$value;
	}

	/**
	 * @param string micro timestamp when saving state occurs
	 * @return string a key that is unique per user request
	 */
	protected function calculateKey($timestamp)
	{
		return $this->getKeyPrefix().':'
			. $this->_page->getRequest()->getUserHostAddress()
			. $this->_page->getPagePath()
			. $timestamp;
	}

	/**
	 * Saves state in cache.
	 * @param mixed state to be stored
	 */
	public function save($state)
	{
		$data=serialize($state);
		$timestamp=(string)microtime(true);
		$key=$this->calculateKey($timestamp);
		$this->getCache()->add($key,$data,$this->_timeout);
		$this->_page->setClientState(TPageStateFormatter::serialize($this->_page,$timestamp));
	}

	/**
	 * Loads page state from cache.
	 * @return mixed the restored state
	 * @throws THttpException if page state is corrupted
	 */
	public function load()
	{
		if(($timestamp=TPageStateFormatter::unserialize($this->_page,$this->_page->getRequestClientState()))!==null)
		{
			$key=$this->calculateKey($timestamp);
			if(($data=$this->getCache()->get($key))!==false)
				return unserialize($data);
		}
		throw new THttpException(400,'cachepagestatepersister_pagestate_corrupted');
	}
}