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 abstract function get($key, &$object);
207 abstract function set($key, $document, $ttl);
209 abstract function delete(Array $key);
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
230 final class ActiveMongo_Cache
232 private static $instance;
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
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'));
258 * Initialize the Cache system, this is done
263 public static function Init()
265 if (self
::$instance) {
268 self
::$instance = new ActiveMongo_Cache
;
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
281 public static function setDriver(CacheDriver
$driver)
284 self
::$instance->driver
= &$driver;
290 * Enable the cache for all classes, even those
291 * which does not has the state property $cacheable
295 public static function enable()
298 self
::$instance->enabled
= TRUE;
304 * Disable the cache for all classes, except those
305 * which has the state property $cacheable =TRUE
309 public static function disable()
312 self
::$instance->enabled
= FALSE;
316 // canUseCache($class) {{{
318 * Return TRUE is the current query
321 * @param string $class Class name
325 final protected function canUseCache($class)
327 if (!$this->driver
InstanceOf CacheDriver
) {
330 $enable = isset($class::$cacheable) ?
$class::$cacheable : $this->enabled
;
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
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);
357 // deleteObject($id) {{{
359 * Delete an object from the cache by its $id
363 final static function deleteObject($id)
366 $self = self
::$instance;
367 $self->driver
->delete(array((string)$id));
371 // mixed getObject($id) {{{
373 * Return an object from the cache, if it doesn't
374 * exists it would return FALSE
377 * @return mixed $object
380 final static function getObject($id)
383 $self = self
::$instance;
384 if (!$self->driver
) {
388 $self->driver
->get((string)$id, $object);
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) {
414 $query_id = $this->getQueryID($query_document);
416 if ($this->driver
->get($query_id, $query_result) === FALSE) {
420 if (!is_array($query_result) ||
count($query_result) == 0) {
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) {
438 $db->where('_id IN', array_values($toquery));
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
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
468 function QuerySave($class, $query_document, $cursor)
470 if (!$this->canUseCache($class)) {
474 $query_id = $this->getQueryID($query_document);
479 foreach ($cursor as $id=>$document) {
480 $ids[$id] = $document['_id'];
481 $docs[$id] = $document;
485 $this->driver
->setMulti($docs, $ttl);
486 $this->driver
->set($query_id, $ids, 3600);
490 // UpdateDocumentHook($class, $document, $obj) {{{
494 * Save or Replace an object (document)
497 * @param string $class Class name
498 * @param object $document Document sent to mongodb
499 * @param object $obj ActiveMongo Object
503 function UpdateDocumentHook($class, $document, $obj)
505 if (!$this->canUseCache($class)) {
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);
527 * vim600: sw=4 ts=4 fdm=marker