- Added methods to CacheDriver: config, isEnabled, flush
[activemongo.git] / lib / plugin / Cache / Cache.php
blob4d0b9848c4ce7c1d5f2ee2ae7337f9e62ebd2f30
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 // config($variable, $value) {{{
206 /**
207 * configuration for the driver
209 * @param string $variable
210 * @param mixed $value
212 * @return NULL
214 function config($variable, $value)
217 // }}}
219 // isEnabled() {{{
220 function isEnabled()
222 return TRUE;
224 // }}}
226 abstract function flush();
228 abstract function get($key, &$object);
230 abstract function set($key, $document, $ttl);
232 abstract function delete(Array $key);
237 * CacheDriver
239 * Plug-in which adds cache capabilities to all
240 * ActiveMongo objects. The cache could be enabled
241 * for all objects (by default disabled), or for specified
242 * objects which has the static property cacheable to TRUE.
244 * At query time is also posible to disable the cache, passing
245 * false to doQuery, also this method will override the cache
246 * values if the query can use cache.
248 * @author César D. Rodas <crodas@php.net>
249 * @license BSD License
250 * @package ActiveMongo
251 * @version 1.0
253 final class ActiveMongo_Cache
255 private static $instance;
256 private $enabled;
257 private $driver;
258 private $driver_enabled;
260 // __construct() {{{
262 * Class contructor
264 * This is class is private, so it can be contructed
265 * only using the singleton interfaz.
267 * This method also setup all needed hooks
269 * @return void
271 private function __construct()
273 ActiveMongo::addEvent('before_query', array($this, 'QueryRead'));
274 ActiveMongo::addEvent('after_query', array($this, 'QuerySave'));
275 ActiveMongo::addEvent('after_create', array($this, 'UpdateDocumentHook'));
276 ActiveMongo::addEvent('after_update', array($this, 'UpdateDocumentHook'));
278 // }}}
280 // Init() {{{
282 * Initialize the Cache system, this is done
283 * automatically.
285 * @return void
287 public static function Init()
289 if (self::$instance) {
290 return;
292 self::$instance = new ActiveMongo_Cache;
294 // }}}
296 // setDriver(CacheDriver $driver) {{{
298 * Set the CacheDriver object that will be used
299 * to cache object, must be a sub-class of CacheDriver
301 * @param CacheDriver $driver
303 * @return void
305 public static function setDriver(CacheDriver $driver)
307 self::Init();
308 self::$instance->driver = &$driver;
309 self::$instance->driver_enabled = FALSE;
312 // }}}
314 // enable() {{{
316 * Enable the cache for all classes, even those
317 * which does not has the state property $cacheable
319 * @return void
321 public static function enable()
323 self::Init();
324 self::$instance->enabled = TRUE;
326 // }}}
328 // config($name, $value) {{{
330 * Pass a configuration to the cache driver
332 * @return mixed
334 public static function config($name, $value)
336 self::Init();
337 $self = self::$instance;
338 if (!$self->driver) {
339 return FALSE;
341 return $self->driver->config($name, $value);
343 // }}}
345 // cacheFailed() {{{
347 * Tell to ActiveMongo_Cache that the driver cache failed
348 * (it throwed some exception). Currently it is disabled
349 * temporarily, in the future it might have a threshold
350 * of error and then disable permanently for the request.
352 * @return void
354 function cacheFailed()
356 /* something went wront, disable the cache */
357 /* temporarily */
358 $this->driver_enabled = FALSE;
360 // }}}
362 // disable() {{{
364 * Disable the cache for all classes, except those
365 * which has the state property $cacheable =TRUE
367 * @return void
369 public static function disable()
371 self::Init();
372 self::$instance->enabled = FALSE;
374 // }}}
376 // isDriverActived() {{{
378 * Check if it has a cache driver and
379 * if it is valid.
381 * @return bool
383 static function isDriverActived()
385 self::Init();
386 $self = self::$instance;
387 if (!$self->driver InstanceOf CacheDriver) {
388 return FALSE;
390 if (!$self->driver_enabled && !$self->driver->isEnabled()) {
391 return FALSE;
393 $self->_driver_enabled = TRUE;
394 return TRUE;
396 // }}}
398 // flushCache() {{{
400 * Delete all the cache content, I can't figureout
401 * how this can be useful, but I'm using for testing :-)
404 static function flushCache()
406 self::Init();
407 $self = self::$instance;
408 if (!$self->driver InstanceOf CacheDriver) {
409 return FALSE;
411 if (!$self->driver_enabled && !$self->driver->isEnabled()) {
412 return FALSE;
414 $self->driver->flush();
416 // }}}
418 // canUseCache($class) {{{
420 * Return TRUE is the current query
421 * can use a cache.
423 * @param string $class Class name
425 * @return bool
427 final protected function canUseCache($class)
429 if (!$this->driver InstanceOf CacheDriver) {
430 return FALSE;
432 if (!$this->driver_enabled) {
433 $enabled = $this->driver->isEnabled();
434 if (!$enabled) {
435 return FALSE;
437 $this->driver_enabled = TRUE;
439 $enable = isset($class::$cacheable) ? $class::$cacheable : $this->enabled;
440 return $enable;
442 // }}}
444 // getQueryID(Array $query_docuement) {{{
446 * Get a ID from a given query, right now it is very
447 * simple, it serialize the query document, it should
448 * be improved to easily delete old queries
450 * @param array $query_document
452 * @return string
454 final protected function getQueryID($query_document)
456 /* TODO: Peform some sort of sorting */
457 /* to treat queries with same parameters but */
458 /* different order equal */
460 $id = $this->driver->serialize($query_document);
462 return sha1($id);
464 // }}}
466 // deleteObject($id) {{{
468 * Delete an object from the cache by its $id
470 * @return void
472 final static function deleteObject($id)
474 self::Init();
475 $self = self::$instance;
476 $self->driver->delete(array((string)$id));
478 // }}}
480 // mixed getObject($id) {{{
482 * Return an object from the cache, if it doesn't
483 * exists it would return FALSE
485 * @param mixed $id
486 * @return mixed $object
489 final static function getObject($id)
491 self::Init();
492 $self = self::$instance;
493 if (!$self->driver) {
494 return FALSE;
496 $object = FALSE;
497 $self->driver->get((string)$id, $object);
499 return $object;
501 // }}}
503 // QueryRead($class, $query_document, &$resultset, $use_cache=TRUE){{{
505 * Return the resultset for the current query from the cache if the
506 * cache is enabled, if the current query can be cacheable and if
507 * it already exists on cache.
509 * @param string $class Class name
510 * @param array $query_document Query sent to mongodb
511 * @param array &$resultset The resultset
512 * @param bool $use_cache True if cache can be used
515 * @return mixed FALSE or NULL
517 function QueryRead($class, $query_document, &$resultset, $use_cache=TRUE)
519 if (!$this->canUseCache($class) || !$use_cache) {
520 return;
522 try {
524 $query_id = $this->getQueryID($query_document);
525 if ($this->driver->get($query_id, $query_result) === FALSE) {
526 return;
529 if (!is_array($query_result) || count($query_result) == 0) {
530 return;
533 $toquery = array();
534 $result = array();
536 $cache_ids = array_combine(array_keys($query_result), array_keys($query_result));
538 $this->driver->getMulti($cache_ids, $result);
540 foreach ($result as $id => $doc) {
541 if (!is_array($doc)) {
542 $toquery[$id] = $query_result[$id];
546 if (count($toquery) > 0) {
547 $db = new $class;
548 $db->where('_id IN', array_values($toquery));
549 $db->doQuery(FALSE);
550 $dresult = array();
551 foreach ($db as $doc) {
552 $dresult[$doc->key()] = $doc->getArray();
554 $this->driver->setMulti($dresult, array());
555 $result = array_merge($result, $dresult);
559 $resultset = new CacheCursor($result);
561 } catch (Exception $e) {
562 /* If any goes wrong it shouldn't interupt the current query */
563 $this->cacheFailed();
564 $resultset = NULL;
567 /* Return FALSE to prevent the execution of
568 * any hook similar hook
570 return FALSE;
572 // }}}
574 // QuerySave($class, $query_document, $cursor) {{{
576 * Save the current resultset into the cache
578 * @param string $class
579 * @param array $query_document
580 * @param MongoCursor $cursor
582 * @return void
584 function QuerySave($class, $query_document, $cursor)
586 if (!$this->canUseCache($class)) {
587 return;
590 $query_id = $this->getQueryID($query_document);
591 $ids = array();
592 $ttl = array();
593 $docs = array();
595 try {
596 foreach ($cursor as $id=>$document) {
597 $ids[$id] = $document['_id'];
598 $docs[$id] = $document;
599 $ttl[$id] = 3600;
601 $this->driver->setMulti($docs, $ttl);
602 $this->driver->set($query_id, $ids, 3600);
603 } catch (Exception $e) {
604 $this->cacheFailed();
608 // }}}
610 // UpdateDocumentHook($class, $document, $obj) {{{
611 /**
612 * Update Hook
614 * Save or Replace an object (document)
615 * into the cache.
617 * @param string $class Class name
618 * @param object $document Document sent to mongodb
619 * @param object $obj ActiveMongo Object
621 * @return NULL
623 function UpdateDocumentHook($class, $document, $obj)
625 if (!$this->canUseCache($class)) {
626 return;
629 if (!isset($obj['_id'])) {
630 if (!isset($document['_id'])) {
631 return; /* Weird condition */
633 $obj['_id'] = $document['_id'];
636 try {
637 $this->driver->set((string)$obj['_id'], $obj, 3600);
638 } catch (Exception $e) {
639 $this->cacheFailed();
642 // }}}
647 * Local variables:
648 * tab-width: 4
649 * c-basic-offset: 4
650 * End:
651 * vim600: sw=4 ts=4 fdm=marker
652 * vim<600: sw=4 ts=4