- Removed debug variables
[activemongo.git] / lib / Cache.php
blob2ae79a515ff6c402e00806e8f40e132e3b443fe6
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 // class CursorCache {{{
39 /**
40 * Cursor used for cached items
42 * Hack for ActiveMongo, fake MongoCursor
43 * subclass that iterates in a given array.
45 * This avoid re-write the main iteration
46 * support at MongoDB, nevertheless this might
47 * be improved in the future.
49 * @author César D. Rodas <crodas@php.net>
50 * @license BSD License
51 * @package ActiveMongo
52 * @version 1.0
55 final class CacheCursor Extends MongoCursor
57 protected $var;
58 protected $size;
59 protected $pos;
61 function __construct(Array $array)
63 $this->var = array_values($array);
64 $this->size = count($array);
65 $this->pos = 0;
68 function reset()
70 $this->pos = -1;
73 function key()
75 return (string)$this->var[$this->pos]['_id'];
78 function current()
80 if (!$this->valid()) {
81 return array();
83 return $this->var[$this->pos];
86 function next()
88 ++$this->pos;
91 function valid()
93 return isset($this->var[$this->pos]);
96 function rewind()
98 $this->reset();
99 $this->next();
102 function getNext()
104 $this->rewind();
105 return $this->var[$this->pos];
108 function count()
110 return count($this->var);
114 // }}}
117 * CacheDriver
119 * Cache base class, each driver must inherit
120 * this class, and must implement each method.
122 * @author César D. Rodas <crodas@php.net>
123 * @license BSD License
124 * @package ActiveMongo
125 * @version 1.0
127 abstract class CacheDriver
130 // Serialization {{{
132 * serialize -- by default with BSON
134 * @param object $object
136 * @return string
138 function serialize($object)
140 return bson_encode($object);
144 * deserialize -- by default with BSON
146 * @param string $string
148 * @return object
150 function deserialize($string)
152 return bson_decode($string);
154 // }}}
156 // void getMulti (Array $keys, Array &$objects) {{{
158 * Simple but inneficient implementation of
159 * the getMulti. It retrieve multiple objects
160 * from the cache that matchs the array of keys.
162 * If the cache supports multiple
163 * get (as memcached does) it should be overrided.
166 * @param array $keys
167 * @param array &$objects
170 function getMulti(Array $keys, Array &$objects)
172 foreach ($keys as $key) {
173 if ($this->get($key, $objects[$key]) === FALSE) {
174 $objects[$key] = FALSE;
178 // }}}
180 // setMulti(Array $objects, Array $ttl) {{{
182 * Simple but inneficient implementation of the
183 * setMulti, it basically push a set of objects
184 * to the cache at once.
186 * If the cache driver support this operation,
187 * this method should be overrided.
189 * @param Array $objects
190 * @param Array $ttl
192 * @retun voie
194 function setMulti(Array $objects, Array $ttl)
196 foreach ($objects as $id => $value) {
197 if (!isset($ttl[$id])) {
198 $ttl[$id] = 3600;
200 $this->set($id, $value, $ttl[$id]);
203 // }}}
205 abstract function get($key, &$object);
207 abstract function set($key, $document, $ttl);
209 abstract function delete(Array $key);
214 * CacheDriver
216 * Plug-in which adds cache capabilities to all
217 * ActiveMongo objects. The cache could be enabled
218 * for all objects (by default disabled), or for specified
219 * objects which has the static property cacheable to TRUE.
221 * At query time is also posible to disable the cache, passing
222 * false to doQuery, also this method will override the cache
223 * values if the query can use cache.
225 * @author César D. Rodas <crodas@php.net>
226 * @license BSD License
227 * @package ActiveMongo
228 * @version 1.0
230 final class ActiveMongo_Cache
232 private static $instance;
233 private $enabled;
234 private $driver;
236 // __construct() {{{
238 * Class contructor
240 * This is class is private, so it can be contructed
241 * only using the singleton interfaz.
243 * This method also setup all needed hooks
245 * @return void
247 private function __construct()
249 ActiveMongo::addEvent('before_query', array($this, 'QueryRead'));
250 ActiveMongo::addEvent('after_query', array($this, 'QuerySave'));
251 ActiveMongo::addEvent('after_create', array($this, 'UpdateDocumentHook'));
252 ActiveMongo::addEvent('after_update', array($this, 'UpdateDocumentHook'));
254 // }}}
256 // Init() {{{
258 * Initialize the Cache system, this is done
259 * automatically.
261 * @return void
263 public static function Init()
265 if (self::$instance) {
266 return;
268 self::$instance = new ActiveMongo_Cache;
270 // }}}
272 // setDriver(CacheDriver $driver) {{{
274 * Set the CacheDriver object that will be used
275 * to cache object, must be a sub-class of CacheDriver
277 * @param CacheDriver $driver
279 * @return void
281 public static function setDriver(CacheDriver $driver)
283 self::Init();
284 self::$instance->driver = &$driver;
286 // }}}
288 // enable() {{{
290 * Enable the cache for all classes, even those
291 * which does not has the state property $cacheable
293 * @return void
295 public static function enable()
297 self::Init();
298 self::$instance->enabled = TRUE;
300 // }}}
302 // disable() {{{
304 * Disable the cache for all classes, except those
305 * which has the state property $cacheable =TRUE
307 * @return void
309 public static function disable()
311 self::Init();
312 self::$instance->enabled = FALSE;
314 // }}}
316 // canUseCache($class) {{{
318 * Return TRUE is the current query
319 * can use a cache.
321 * @param string $class Class name
323 * @return bool
325 final protected function canUseCache($class)
327 if (!$this->driver InstanceOf CacheDriver) {
328 return FALSE;
330 $enable = isset($class::$cacheable) ? $class::$cacheable : $this->enabled;
331 return $enable;
333 // }}}
335 // getQueryID(Array $query_docuement) {{{
337 * Get a ID from a given query, right now it is very
338 * simple, it serialize the query document, it should
339 * be improved to easily delete old queries
341 * @param array $query_document
343 * @return string
345 final protected function getQueryID($query_document)
347 /* TODO: Peform some sort of sorting */
348 /* to treat queries with same parameters but */
349 /* different order equal */
351 $id = $this->driver->serialize($query_document);
353 return sha1($id);
355 // }}}
357 // deleteObject($id) {{{
359 * Delete an object from the cache by its $id
361 * @return void
363 final static function deleteObject($id)
365 self::Init();
366 $self = self::$instance;
367 $self->driver->delete(array((string)$id));
369 // }}}
371 // mixed getObject($id) {{{
373 * Return an object from the cache, if it doesn't
374 * exists it would return FALSE
376 * @param mixed $id
377 * @return mixed $object
380 final static function getObject($id)
382 self::Init();
383 $self = self::$instance;
384 if (!$self->driver) {
385 return FALSE;
387 $object = FALSE;
388 $self->driver->get((string)$id, $object);
390 return $object;
392 // }}}
394 // QueryRead($class, $query_document, &$resultset, $use_cache=TRUE){{{
396 * Return the resultset for the current query from the cache if the
397 * cache is enabled, if the current query can be cacheable and if
398 * it already exists on cache.
400 * @param string $class Class name
401 * @param array $query_document Query sent to mongodb
402 * @param array &$resultset The resultset
403 * @param bool $use_cache True if cache can be used
406 * @return mixed FALSE or NULL
408 function QueryRead($class, $query_document, &$resultset, $use_cache=TRUE)
410 if (!$this->canUseCache($class) || !$use_cache) {
411 return;
414 $query_id = $this->getQueryID($query_document);
416 if ($this->driver->get($query_id, $query_result) === FALSE) {
417 return;
420 if (!is_array($query_result) || count($query_result) == 0) {
421 return;
424 $toquery = array();
425 $result = array();
427 $cache_ids = array_combine(array_keys($query_result), array_keys($query_result));
428 $this->driver->getMulti($cache_ids, $result);
430 foreach ($result as $id => $doc) {
431 if (!is_array($doc)) {
432 $toquery[$id] = $query_result[$id];
436 if (count($toquery) > 0) {
437 $db = new $class;
438 $db->where('_id IN', array_values($toquery));
439 $db->doQuery(FALSE);
440 $dresult = array();
441 foreach ($db as $doc) {
442 $dresult[$doc->key()] = $doc->getArray();
444 $this->driver->setMulti($dresult, array());
445 $result = array_merge($result, $dresult);
449 $resultset = new CacheCursor($result);
451 /* Return FALSE to prevent the execution of
452 * any hook similar hook
454 return FALSE;
456 // }}}
458 // QuerySave($class, $query_document, $cursor) {{{
460 * Save the current resultset into the cache
462 * @param string $class
463 * @param array $query_document
464 * @param MongoCursor $cursor
466 * @return void
468 function QuerySave($class, $query_document, $cursor)
470 if (!$this->canUseCache($class)) {
471 return;
474 $query_id = $this->getQueryID($query_document);
475 $ids = array();
476 $ttl = array();
477 $docs = array();
479 foreach ($cursor as $id=>$document) {
480 $ids[$id] = $document['_id'];
481 $docs[$id] = $document;
482 $ttl[$id] = 3600;
485 $this->driver->setMulti($docs, $ttl);
486 $this->driver->set($query_id, $ids, 3600);
488 // }}}
490 // UpdateDocumentHook($class, $document, $obj) {{{
491 /**
492 * Update Hook
494 * Save or Replace an object (document)
495 * into the cache.
497 * @param string $class Class name
498 * @param object $document Document sent to mongodb
499 * @param object $obj ActiveMongo Object
501 * @return NULL
503 function UpdateDocumentHook($class, $document, $obj)
505 if (!$this->canUseCache($class)) {
506 return;
509 if (!isset($obj['_id'])) {
510 if (!isset($document['_id'])) {
511 return; /* Weird condition */
513 $obj['_id'] = $document['_id'];
516 $this->driver->set((string)$obj['_id'], $obj, 3600);
518 // }}}
523 * Local variables:
524 * tab-width: 4
525 * c-basic-offset: 4
526 * End:
527 * vim600: sw=4 ts=4 fdm=marker
528 * vim<600: sw=4 ts=4