# Source for file Cache.php

Documentation is available at Cache.php

1. <?php
2. /*
3. * $Id: Cache.php 1857 2007-06-26 22:30:23Z subzero2000 $
4. *
5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16. *
17. * This software consists of voluntary contributions made by many individuals
18. * and is licensed under the LGPL. For more information, see
19. * <http://www.phpdoctrine.com>.
20. */
21. Doctrine::autoload('Doctrine_EventListener');
22. /**
23. * Doctrine_Cache
24. *
25. * @package Doctrine
26. * @subpackage Doctrine_Cache
27. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
28. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
29. * @category Object Relational Mapping
30. * @link www.phpdoctrine.com
31. * @since 1.0
32. * @version $Revision: 1857 $
33. */
34. class Doctrine_Cache extends Doctrine_EventListener implements Countable, IteratorAggregate
35. {
36. /**
37. * @var array $_options an array of general caching options
38. */
39. protected $_options = array('size' => 1000,
40. 'lifeTime' => 3600,
41. 'addStatsPropability' => 0.25,
42. 'savePropability' => 0.10,
43. 'cleanPropability' => 0.01,
44. 'statsFile' => '../data/stats.cache',
45. );
46. /**
47. * @var array $_queries query stack
48. */
49. protected $_queries = array();
50. /**
51. * @var Doctrine_Cache_Interface $_driver the cache driver object
52. */
53. protected $_driver;
54. /**
55. * @var array $data current cache data array
56. */
57. protected $_data = array();
58. /**
59. * @var boolean $success the success of last operation
60. */
61. protected $_success = false;
62. /**
63. * constructor
64. *
65. * @param Doctrine_Cache_Interface|string$driver cache driver name or a driver object
66. * @param array $options cache driver options
67. */
68. public function __construct($driver, $options = array())
69. {
70. if (is_object($driver)) {
71. if ( ! ($driver instanceof Doctrine_Cache_Interface)) {
72. throw new Doctrine_Cache_Exception('Driver should implement Doctrine_Cache_Interface.');
73. }
75. $this->_driver = $driver;
76. $this->_driver->setOptions($options);
77. } else {
78. $class = 'Doctrine_Cache_' . ucwords(strtolower($driver));
80. if ( ! class_exists($class)) {
81. throw new Doctrine_Cache_Exception('Cache driver ' . $driver . ' could not be found.');
82. }
84. $this->_driver = new $class($options);
85. }
86. }
87. /**
88. * getDriver
89. * returns the current cache driver
90. *
91. * @return Doctrine_Cache_Driver
92. */
93. public function getDriver()
94. {
95. return $this->_driver;
96. }
97. /**
98. * setOption
99. *
100. * @param mixed $option the option name
101. * @param mixed $value option value
102. * @return boolean TRUE on success, FALSE on failure
103. */
104. public function setOption($option, $value)
105. {
106. // sanity check (we need this since we are using isset() instead of array_key_exists())
107. if ($value === null) {
108. throw new Doctrine_Cache_Exception('Null values not accepted for options.');
109. }
111. if (isset($this->_options[$option])) {
112. $this->_options[$option] = $value;
113. return true;
114. }
115. return false;
116. }
117. /**
118. * getOption
119. *
120. * @param mixed $option the option name
121. * @return mixed option value
122. */
123. public function getOption($option)
124. {
125. if ( ! isset($this->_options[$option])) {
126. throw new Doctrine_Cache_Exception('Unknown option ' . $option);
127. }
129. return $this->_options[$option];
130. }
131. /**
132. * add
133. * adds a query to internal query stack
134. *
135. * @param string|array$query sql query string
136. * @param string $namespace connection namespace
137. * @return void
138. */
139. public function add($query, $namespace = null)
140. {
141. if (isset($namespace)) {
142. $this->_queries[$namespace][] = $query;
143. } else {
144. $this->_queries[] = $query;
145. }
146. }
147. /**
148. * getQueries
149. *
150. * @param string $namespace optional query namespace
151. * @return array an array of sql query strings
152. */
153. public function getAll($namespace = null)
154. {
155. if (isset($namespace)) {
156. if( ! isset($this->_queries[$namespace])) {
157. return array();
158. }
160. return $this->_queries[$namespace];
161. }
163. return $this->_queries;
164. }
165. /**
166. * pop
167. *
168. * pops a query from the stack
169. * @return string
170. */
171. public function pop()
172. {
173. return array_pop($this->_queries);
174. }
175. /**
176. * reset
177. *
178. * removes all queries from the query stack
179. * @return void
180. */
181. public function reset()
182. {
183. $this->_queries = array();
184. }
185. /**
186. * count
187. *
188. * @return integer the number of queries in the stack
189. */
190. public function count()
191. {
192. return count($this->_queries);
193. }
194. /**
195. * getIterator
196. *
197. * @return ArrayIterator an iterator that iterates through the query stack
198. */
199. public function getIterator()
200. {
201. return new ArrayIterator($this->_queries);
202. }
203. /**
204. * @return boolean whether or not the last cache operation was successful
205. */
206. public function isSuccessful()
207. {
208. return $this->_success;
209. }
210. /**
211. * save
212. *
213. * @return boolean
214. */
215. public function clean()
216. {
217. $rand = (mt_rand() / mt_getrandmax());
219. if ($rand <= $this->_options['cleanPropability']) {
220. $queries = $this->readStats();
222. $stats = array();
224. foreach ($queries as $query) {
225. if (isset($stats[$query])) {
226. $stats[$query]++;
227. } else {
228. $stats[$query] = 1;
229. }
230. }
231. sort($stats);
233. $i = $this->_options['size'];
235. while ($i--) {
236. $element = next($stats);
237. $query = key($stats);
239. $hash = md5($query);
241. $this->_driver->delete($hash);
242. }
243. }
244. }
245. /**
246. * readStats
247. *
248. * @return array
249. */
250. public function readStats()
251. {
252. if ($this->_options['statsFile'] !== false) {
253. $content = file_get_contents($this->_options['statsFile']);
255. $e = explode("\n", $content);
257. return array_map('unserialize', $e);
258. }
259. return array();
260. }
261. /**
262. * appendStats
263. *
264. * adds all queries to stats file
265. * @return void
266. */
267. public function appendStats()
268. {
269. if ($this->_options['statsFile'] !== false) {
271. if ( ! file_exists($this->_options['statsFile'])) {
272. throw new Doctrine_Cache_Exception("Couldn't save cache statistics. Cache statistics file doesn't exists!");
273. }
275. $rand = (mt_rand() / mt_getrandmax());
277. if ($rand <= $this->_options['addStatsPropability']) {
278. file_put_contents($this->_options['statsFile'], implode("\n", array_map('serialize', $this->_queries)));
279. }
280. }
281. }
282. /**
283. * preQuery
284. * listens on the Doctrine_Event preQuery event
285. *
286. * adds the issued query to internal query stack
287. * and checks if cached element exists
288. *
289. * @return boolean
290. */
291. public function preQuery(Doctrine_Event $event)
292. {
293. $query = $event->getQuery();
295. $data = false;
296. // only process SELECT statements
297. if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') {
299. $this->add($query, $event->getInvoker()->getName());
301. $data = $this->_driver->fetch(md5(serialize($query)));
303. $this->success = ($data) ? true : false;
305. if ( ! $data) {
306. $rand = (mt_rand() / mt_getrandmax());
308. if ($rand < $this->_options['savePropability']) {
309. $stmt = $event->getInvoker()->getAdapter()->query($query);
311. $data = $stmt->fetchAll(Doctrine::FETCH_ASSOC);
313. $this->success = true;
315. $this->_driver->save(md5(serialize($query)), $data);
316. }
317. }
318. if ($this->success)
319. {
320. $this->_data = $data;
321. return true;
322. }
323. }
324. return false;
325. }
326. /**
327. * preFetch
328. * listens the preFetch event of Doctrine_Connection_Statement
329. *
330. * advances the internal pointer of cached data and returns
331. * the current element
332. *
333. * @return array
334. */
335. public function preFetch(Doctrine_Event $event)
336. {
337. $ret = current($this->_data);
338. next($this->_data);
339. return $ret;
340. }
341. /**
342. * preFetch
343. * listens the preFetchAll event of Doctrine_Connection_Statement
344. *
345. * returns the current cache data array
346. *
347. * @return array
348. */
349. public function preFetchAll(Doctrine_Event $event)
350. {
351. return $this->_data;
352. }
353. /**
354. * preExecute
355. * listens the preExecute event of Doctrine_Connection_Statement
356. *
357. * adds the issued query to internal query stack
358. * and checks if cached element exists
359. *
360. * @return boolean
361. */
362. public function preExecute(Doctrine_Event $event)
363. {
364. $query = $event->getQuery();
366. $data = false;
368. // only process SELECT statements
369. if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') {
371. $this->add($query, $event->getInvoker()->getDbh()->getName());
373. $data = $this->_driver->fetch(md5(serialize(array($query, $event->getParams()))));
375. $this->success = ($data) ? true : false;
377. if ( ! $data) {
378. $rand = (mt_rand() / mt_getrandmax());
380. if ($rand <= $this->_options['savePropability']) {
382. $stmt = $event->getInvoker()->getStatement();
384. $stmt->execute($event->getParams());
386. $data = $stmt->fetchAll(Doctrine::FETCH_ASSOC);
388. $this->success = true;
390. $this->_driver->save(md5(serialize(array($query, $event->getParams()))), $data);
391. }
392. }
393. if ($this->success)
394. {
395. $this->_data = $data;
396. return true;
397. }
398. }
399. return false;
400. }
401. }