- Improved Caching, now it is sort of functional
[activemongo.git] / lib / Cache.php
blob876d84137029fbb5071cc7f836e0886deed0bc11
1 <?php
2 /*
3 +---------------------------------------------------------------------------------+
4 | Copyright (c) 2010 ActiveMongo |
5 +---------------------------------------------------------------------------------+
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met: |
8 | 1. Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | 3. All advertising materials mentioning features or use of this software |
16 | must display the following acknowledgement: |
17 | This product includes software developed by César D. Rodas. |
18 | |
19 | 4. Neither the name of the César D. Rodas nor the |
20 | names of its contributors may be used to endorse or promote products |
21 | derived from this software without specific prior written permission. |
22 | |
23 | THIS SOFTWARE IS PROVIDED BY CÉSAR D. RODAS ''AS IS'' AND ANY |
24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
26 | DISCLAIMED. IN NO EVENT SHALL CÉSAR D. RODAS BE LIABLE FOR ANY |
27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
30 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
33 +---------------------------------------------------------------------------------+
34 | Authors: César Rodas <crodas@php.net> |
35 +---------------------------------------------------------------------------------+
38 /**
39 * Cursor used for cached items
41 class CacheCursor Extends MongoCursor
43 protected $var;
44 protected $size;
45 protected $pos;
47 function __construct()
51 function setResultArray(Array $array)
53 $this->var = array_values($array);
54 $this->size = count($array);
55 $this->pos = 0;
58 function reset()
60 $this->pos = -1;
63 function key()
65 return (string)$this->var[$this->pos]['_id'];
68 function current()
70 if (!$this->valid()) {
71 return array();
73 return $this->var[$this->pos];
76 function next()
78 ++$this->pos;
81 function valid()
83 return isset($this->var[$this->pos]);
86 function rewind()
88 $this->reset();
89 $this->next();
92 function getNext()
94 $this->rewind();
95 return $this->var[$this->pos];
98 function count()
100 return count($this->var);
103 function getID()
105 return $this->_id;
109 abstract class CacheDriver
112 // Serialization {{{
114 * serialize -- by default with BSON
116 * @param object $object
118 * @return string
120 function serialize($object)
122 return bson_encode($object);
126 * deserialize -- by default with BSON
128 * @param string $string
130 * @return object
132 function deserialize($string)
134 return bson_decode($string);
136 // }}}
138 abstract function get($key, &$object);
140 abstract function set($key, $document, $ttl);
142 function getMulti(Array $keys, Array &$object)
144 foreach ($keys as $key) {
145 if ($this->get($key, $object[$key]) === FALSE) {
146 $object[$key] = FALSE;
151 function setMulti(Array $objects, Array $ttl)
153 foreach ($objects as $id => $value) {
154 if (!isset($ttl[$id])) {
155 $ttl[$id] = 3600;
157 $this->set($id, $value, $ttl[$id]);
161 abstract function delete(Array $key);
164 final class ActiveMongo_Cache
166 private static $instance;
167 private $enabled;
168 private $driver;
170 private function __construct()
172 ActiveMongo::addEvent('before_query', array($this, 'QueryRead'));
173 ActiveMongo::addEvent('after_query', array($this, 'QuerySave'));
174 ActiveMongo::addEvent('after_create', array($this, 'UpdateHook'));
175 ActiveMongo::addEvent('after_update', array($this, 'UpdateHook'));
178 public static function Init()
180 if (self::$instance) {
181 return;
183 self::$instance = new ActiveMongo_Cache;
186 public static function setDriver(CacheDriver $driver)
188 self::Init();
189 self::$instance->driver = $driver;
192 public static function enable()
194 self::Init();
195 self::$instance->enabled = TRUE;
198 public static function disable()
200 self::Init();
201 self::$instance->enabled = FALSE;
204 final protected function hasCache($class)
206 if (!$this->driver InstanceOf CacheDriver) {
207 return FALSE;
209 $enable = isset($class::$cacheable) ? $class::$cacheable : $this->enabled;
210 return $enable;
213 final protected function getQueryID($query_document)
215 /* TODO: Peform some sort of sorting */
216 /* to treat queries with same parameters but */
217 /* different order equal */
219 $id = $this->driver->serialize($query_document);
221 return sha1($id);
227 function QueryRead($class, $query_document, &$resultset)
229 if (!$this->hasCache($class)) {
230 return;
233 $query_id = $this->getQueryID($query_document);
235 if ($this->driver->get($query_id, $query_result) === FALSE) {
236 return;
239 if (!is_array($query_result) || count($query_result) == 0) {
240 return;
243 $toquery = array();
244 $result = array();
246 $cache_ids = array_combine(array_keys($query_result), array_keys($query_result));
247 $this->driver->getMulti($cache_ids, $result);
249 foreach ($result as $id => $doc) {
250 if (!is_array($doc)) {
251 $toquery[] = $id;
255 if (count($toquery) > 0) {
256 $db = new $class;
257 $db->where('_id IN', array_values($toquery));
258 foreach ($db as $doc) {
259 foreach ($toquery as $id) {
260 if ($id == $doc['_id']) {
261 break;
264 $result[$id] = $doc;
268 $resultset = new CacheCursor;
269 $resultset->setResultArray($result);
271 /* Return FALSE to prevent the execution of
272 * any hook similar hook
274 return FALSE;
281 function QuerySave($class, $query_document, $cursor)
283 if (!$this->hasCache($class)) {
284 return;
287 $query_id = $this->getQueryID($query_document);
288 $ids = array();
289 $ttl = array();
290 $docs = array();
292 foreach ($cursor as $id=>$document) {
293 $ids[$id] = $document['_id'];
294 $docs[$id] = $document;
295 $ttl[$id] = 3600;
298 $this->driver->setMulti($docs, $ttl);
299 $this->driver->set($query_id, $ids, 3600);
302 // UpdateHook($class, $document, $obj) {{{
303 /**
304 * Update Hook
306 * Save or Replace an object (document)
307 * into the cache.
309 * @param string $class Class name
310 * @param object $document Document sent to mongodb
311 * @param object $obj ActiveMongo Object
313 * @return NULL
315 function UpdateHook($class, $document, $obj)
317 if (!$this->hasCache($class)) {
318 return;
321 if (!isset($obj['_id'])) {
322 if (!isset($document['_id'])) {
323 return; /* Weird condition */
325 $obj['_id'] = $document['_id'];
328 $this->driver->set((string)$obj['_id'], $obj, 3600);
330 // }}}
334 ActiveMongo_Cache::Init();
337 * Local variables:
338 * tab-width: 4
339 * c-basic-offset: 4
340 * End:
341 * vim600: sw=4 ts=4 fdm=marker
342 * vim<600: sw=4 ts=4