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. |
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. |
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. |
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. |
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 {{{
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
55 final class CacheCursor
Extends MongoCursor
61 function __construct(Array $array)
63 $this->var = array_values($array);
64 $this->size
= count($array);
75 return (string)$this->var[$this->pos
]['_id'];
80 if (!$this->valid()) {
83 return $this->var[$this->pos
];
93 return isset($this->var[$this->pos
]);
105 return $this->var[$this->pos
];
110 return count($this->var);
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
127 abstract class CacheDriver
132 * serialize -- by default with BSON
134 * @param object $object
138 function serialize($object)
140 return bson_encode($object);
144 * deserialize -- by default with BSON
146 * @param string $string
150 function deserialize($string)
152 return bson_decode($string);
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.
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;
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
194 function setMulti(Array $objects, Array $ttl)
196 foreach ($objects as $id => $value) {
197 if (!isset($ttl[$id])) {
200 $this->set($id, $value, $ttl[$id]);
205 // config($variable, $value) {{{
207 * configuration for the driver
209 * @param string $variable
210 * @param mixed $value
214 function config($variable, $value)
226 abstract function flush();
228 abstract function get($key, &$object);
230 abstract function set($key, $document, $ttl);
232 abstract function delete(Array $key);
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
253 final class ActiveMongo_Cache
255 private static $instance;
258 private $driver_enabled;
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
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'));
282 * Initialize the Cache system, this is done
287 public static function Init()
289 if (self
::$instance) {
292 self
::$instance = new ActiveMongo_Cache
;
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
305 public static function setDriver(CacheDriver
$driver)
308 self
::$instance->driver
= &$driver;
309 self
::$instance->driver_enabled
= FALSE;
316 * Enable the cache for all classes, even those
317 * which does not has the state property $cacheable
321 public static function enable()
324 self
::$instance->enabled
= TRUE;
328 // config($name, $value) {{{
330 * Pass a configuration to the cache driver
334 public static function config($name, $value)
337 $self = self
::$instance;
338 if (!$self->driver
) {
341 return $self->driver
->config($name, $value);
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.
354 function cacheFailed()
356 /* something went wront, disable the cache */
358 $this->driver_enabled
= FALSE;
364 * Disable the cache for all classes, except those
365 * which has the state property $cacheable =TRUE
369 public static function disable()
372 self
::$instance->enabled
= FALSE;
376 // isDriverActived() {{{
378 * Check if it has a cache driver and
383 static function isDriverActived()
386 $self = self
::$instance;
387 if (!$self->driver
InstanceOf CacheDriver
) {
390 if (!$self->driver_enabled
&& !$self->driver
->isEnabled()) {
393 $self->_driver_enabled
= TRUE;
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()
407 $self = self
::$instance;
408 if (!$self->driver
InstanceOf CacheDriver
) {
411 if (!$self->driver_enabled
&& !$self->driver
->isEnabled()) {
414 $self->driver
->flush();
418 // canUseCache($class) {{{
420 * Return TRUE is the current query
423 * @param string $class Class name
427 final protected function canUseCache($class)
429 if (!$this->driver
InstanceOf CacheDriver
) {
432 if (!$this->driver_enabled
) {
433 $enabled = $this->driver
->isEnabled();
437 $this->driver_enabled
= TRUE;
439 $enable = isset($class::$cacheable) ?
$class::$cacheable : $this->enabled
;
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
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);
466 // deleteObject($id) {{{
468 * Delete an object from the cache by its $id
472 final static function deleteObject($id)
475 $self = self
::$instance;
476 $self->driver
->delete(array((string)$id));
480 // mixed getObject($id) {{{
482 * Return an object from the cache, if it doesn't
483 * exists it would return FALSE
486 * @return mixed $object
489 final static function getObject($id)
492 $self = self
::$instance;
493 if (!$self->driver
) {
497 $self->driver
->get((string)$id, $object);
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) {
524 $query_id = $this->getQueryID($query_document);
525 if ($this->driver
->get($query_id, $query_result) === FALSE) {
529 if (!is_array($query_result) ||
count($query_result) == 0) {
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) {
548 $db->where('_id IN', array_values($toquery));
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();
567 /* Return FALSE to prevent the execution of
568 * any hook similar hook
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
584 function QuerySave($class, $query_document, $cursor)
586 if (!$this->canUseCache($class)) {
590 $query_id = $this->getQueryID($query_document);
596 foreach ($cursor as $id=>$document) {
597 $ids[$id] = $document['_id'];
598 $docs[$id] = $document;
601 $this->driver
->setMulti($docs, $ttl);
602 $this->driver
->set($query_id, $ids, 3600);
603 } catch (Exception
$e) {
604 $this->cacheFailed();
610 // UpdateDocumentHook($class, $document, $obj) {{{
614 * Save or Replace an object (document)
617 * @param string $class Class name
618 * @param object $document Document sent to mongodb
619 * @param object $obj ActiveMongo Object
623 function UpdateDocumentHook($class, $document, $obj)
625 if (!$this->canUseCache($class)) {
629 if (!isset($obj['_id'])) {
630 if (!isset($document['_id'])) {
631 return; /* Weird condition */
633 $obj['_id'] = $document['_id'];
637 $this->driver
->set((string)$obj['_id'], $obj, 3600);
638 } catch (Exception
$e) {
639 $this->cacheFailed();
651 * vim600: sw=4 ts=4 fdm=marker