diff --git a/readme.md b/readme.md index 9f53c97..cb8d108 100755 --- a/readme.md +++ b/readme.md @@ -921,6 +921,81 @@ This extension does not rely on `EMongoCriteria` internally. So I expect all modifications to certain parts of MongoYii to be compatible with and without `EMongoCriteria`. +# Utilities + +The `util` folder contains general awesome extensions to MongoYii that people may find useful. The sort of things that count as part of this folder are replacements for internal pieces +of Yii that might seem outside of the scope of the root of this repository. + +THESE ARE NOT MADE BY THE AUTHOR OF MONGOYII. That being said I will attempt to maintain them. + +## EMongoCache + +This is a MongoYii implementation of `CCache` by [Rajcsányi Zoltán](http://ezmegaz.hu/). + +To use it first place it in your configuration: + +'components'=>array( + ... + 'cache' => array( + 'class'=>'application.extensions.MongoYii.util.EMongoCache', + // 'ensureIndex' => true, //set to false after first use of the cache + // 'mongoConnectionId' => 'mongodb', + // 'collectionName' => 'mongodb_cache', + ), +} + +The commented out lines are optional parameters you can send in if required. + +And now an example of its usage: + + // flush cache + Yii::app()->cache->flush(); + + // add data to cache + Yii::app()->cache->set('apple', 'fruit'); + Yii::app()->cache->set('onion', 'vegetables'); + Yii::app()->cache->set(1, 'one'); + Yii::app()->cache->set(2, 'two'); + Yii::app()->cache->set('one', 1); + Yii::app()->cache->set('two', 2); + + // delete from cache + Yii::app()->cache->delete(1); + Yii::app()->cache->delete('two'); + + // read from cache + echo Yii::app()->cache->get(2); + + // multiple read from cache + $arr = Yii::app()->cache->mget(array('apple', 1, 'two')); + + print_r($arr); // Array( [apple] => fruit [1] => 'one' [two] => 2 ) + +## EMongoMessageSource + +This is a MongoYii `Yii::t()` implementation by [Rajcsányi Zoltán](http://ezmegaz.hu/). + +To use it first add it to your configuration: + + 'components' => array( + ... + 'messages' => array( + 'class' => 'application.extensions.MongoYii.util.EMongoMessageSource', + // 'mongoConnectionId' => 'mongodb', + // 'collectionName' => 'YiiMessages', + ) + ) + +The commented out lines are optional parameters you can send in if required. + +And then add some messages to the translation table: + + db.YiiMessages.insert( { category:"users", message:"Freund", translations: [ {language:"eng", message:"Friend"} ] } ); + +And then simply get that message: + + + ## Upgrade Notes There has been a small but dramatic change between version 1.x and 2.x of MongoYii. The `compare()` function within the `EMongoCriteria` now no longer uses partial matching by diff --git a/util/EMongoCache.php b/util/EMongoCache.php new file mode 100644 index 0000000..3f16b17 --- /dev/null +++ b/util/EMongoCache.php @@ -0,0 +1,282 @@ += 1.5.3 + * required extensions: MongoYii (for the configuration of the mongoDB connection) + * + * @author Zoltan Rajcsanyi + * @copyright 2013 Zoltan Rajcsanyi + * @license New BSD License + * @category Database + * @version 1.0 + */ + +/** + * EMongoCache implements a cache application component by storing cached data in a mongodb. + * + * EMongoDBCache stores cache data in a collection named {@link collectionName}. + * If the collection does not exist, it will be automatically created. + * + * You can also specify {@link mongoConnectionId} to select a mongoDb + * + * See {@link CCache} manual for common cache operations that are supported by EMongoCache. + */ +class EMongoCache extends CCache +{ + /** + * @var string the ID of a {@link CDbConnection} application component. + */ + public $mongoConnectionId='mongodb'; // public $mongoConnectionId = 'mongodb'; + + public $connectionID; + + /** + * @var string name of the collection to store cache content. Defaults to 'YiiCache'. + * The items are stored withe following structure: + *
+	 *  key: string,
+	 *  value: string
+	 *  expire: integer
+	 * 
+ */ + public $collectionName = 'YiiCache'; + public $ensureIndex = true; //set to false after first use of the cache + + /** + * @var EMongoClient the DB connection instance + */ + private $_db; + + private $_gcProbability=100; + private $_gced=false; + + /** + * Initializes this application component. + * ensureIndex 'key' if $ensureIndex = true + * Set $ensureIndex to false after first use to increase performance + * + * This method is required by the {@link IApplicationComponent} interface. + * It ensures the existence of the cache DB table. + * It also removes expired data items from the cache. + */ + public function init() + { + parent::init(); + + if ($this->ensureIndex) { + $this->getCollection()->ensureIndex( array('key' => 1)); // create index on "key" + } + } + + /** + * @return EMongoClient the DB connection instance + * @throws CException if {@link connectionID} does not point to a valid application component. + */ + protected function getDbConnection() + { + if ($this->_db !== null) { + return $this->_db; + } elseif (($id=$this->connectionID) !==null) { + if (($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection) { + return $this->_db; + } else { + throw new CException(Yii::t('yii','EMongoCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a EMongoClient application component.', + array('{id}'=>$id))); + } + } else { + return $this->_db=Yii::app()->getComponent('mongodb'); + } + } + + /** + * Returns current MongoCollection object + * + * @return MongoCollection + */ + protected function getCollection() + { + return $this->getDbConnection()->{$this->collectionName}; + } + + /** + * @return integer the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + * @since 1.0.9 + */ + public function getGCProbability() + { + return $this->_gcProbability; + } + + /** + * @param integer $value the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. + * @since 1.0.9 + */ + public function setGCProbability($value) + { + $value=(int)$value; + + if ($value < 0) { + $value=0; + } elseif ($value > 1000000) { + $value=1000000; + } + + $this->_gcProbability=$value; + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + $time=time(); + + $criteria = array( + 'key' => (string)$key, + '$or' => array( + array('expire' => 0), + array('expire' => array('$gt'=> $time)), + ), + ); + + $data = $this->getCollection()->findOne($criteria); + + return $data===null?'':$data['value']; + } + + /** + * Retrieves multiple values from cache with the specified keys. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + if (empty($keys)) { + return array(); + } + + $results=array(); + $time=time(); + + $criteria = array( + 'key' => array('$in'=>$keys), + '$or' => array( + array('expire' => 0), + array('expire' => array('$gt'=> $time)), + ), + ); + + + $data = $this->getCollection()->find($criteria); + + if (!empty($data) && $data->count()) { + foreach ($data as $id => $value) { + $results[$value['key']] = $value['value']; + } + } + + return $results; + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $expire) + { + return $this->addValue($key, $value, $expire); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * If the key exists the value will be updated, otherwise inserted + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $expire) + { + if (!$this->_gced && mt_rand(0,1000000) < $this->_gcProbability) { + $this->gc(); + $this->_gced=true; + } + + if ($expire > 0) { + $expire+=time(); + } else { + $expire=0; + } + + $criteria = array('key' => (string)$key); + + $data = array( + 'key' => (string)$key, + 'value' => (string)$value, + 'expire' => (int)$expire, + ); + + $options = array('upsert'=>true); + + try { + return $this->getCollection()->update($criteria, $data, $options); + } catch(Exception $e) { + return false; + } + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + $criteria = array('key' => (string)$key); + $data = $this->getCollection()->remove($criteria); + return true; + } + + /** + * Removes the expired data values. + */ + protected function gc() + { + //delete expired entries + $criteria = array( + 'expired' => array('$gt' => 0), + 'expired' => array('$lt' => time()), + ); + + $this->getCollection()->remove($criteria); + + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + $this->getCollection()->remove(); + return true; + } +} \ No newline at end of file diff --git a/util/EMongoMessageSource.php b/util/EMongoMessageSource.php new file mode 100644 index 0000000..76b2466 --- /dev/null +++ b/util/EMongoMessageSource.php @@ -0,0 +1,143 @@ + + * @copyright 2013 Zoltan Rajcsanyi + * @license New BSD License + * @category Database + * @version 1.0 + */ + +/** + * EMongoMessageSource represents a message source that stores translated messages in MongoDB. + * + * PHP version 5.2+ + * MongoDB version >= 1.5.3 + * required extensions: MongoYii (for the configuration of the mongoDB connection)* + * + * The YiiMessages collection contains the following schema: + *
+ *   _id: mongoId(),
+ *   category: string,
+ *   message: string,
+ *   translations: [language: string, message: string]
+ * 
+ * + * The 'YiiMessages' collection can be customized by setting {@link collectionName}. + * + * When {@link cachingDuration} is set as a positive number, message translations will be cached. + * + * @property EMongoConnection $emongoConnection The DB connection used for the message source. + * + * @author Zoltan Rajcsanyi + * @copyright 2013 Zoltan Rajcsanyi + * @license New BSD License + * @category Database + * @version 1.0 + */ +class EMongoMessageSource extends CMessageSource +{ + const CACHE_KEY_PREFIX='Yii.EMongoMessageSource.'; + /** + * @var string the ID of the database connection application component. Defaults to 'mongodb'. + */ + public $mongoConnectionId; + public $connectionID; + + /** + * @var string name of collection to store messages and translations + */ + public $collectionName = 'YiiMessages'; + + /** + * @var integer the time in seconds that the messages can remain valid in cache. + * Defaults to 0, meaning the caching is disabled. + */ + public $cachingDuration=0; + /** + * @var string the ID of the cache application component that is used to cache the messages. + * Defaults to 'cache' which refers to the primary cache application component. + * Set this property to false if you want to disable caching the messages. + */ + public $cacheID='cache'; + + /** + * @var EMongoClient the DB connection instance + */ + private $_db; + + /** + * Loads the message translation for the specified language and category. + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages + */ + protected function loadMessages($category,$language) + { + if($this->cachingDuration>0 && $this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null) + { + $key=self::CACHE_KEY_PREFIX.'.messages.'.$category.'.'.$language; + if(($data=$cache->get($key))!==false) + return unserialize($data); + } + + $messages=$this->loadMessagesFromDb($category,$language); + + if(isset($cache)) + $cache->set($key,serialize($messages),$this->cachingDuration); + + return $messages; + } + + /** + * @return EMongoClient the DB connection instance + * @throws CException if {@link connectionID} does not point to a valid application component. + */ + protected function getDbConnection() + { + if ($this->_db !== null) { + return $this->_db; + } elseif (($id=$this->connectionID) !==null) { + if (($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection) { + return $this->_db; + } else { + throw new CException(Yii::t('yii','EMongoCache.connectionID "{id}" is invalid. Please make sure it refers to the ID of a EMongoClient application component.', + array('{id}'=>$id))); + } + } else { + return $this->_db=Yii::app()->getComponent('mongodb'); + } + } + + /** + * Returns current MongoCollection object + * + * @return MongoCollection + */ + protected function getCollection() + { + return $this->getDbConnection()->{$this->collectionName}; + } + + /** + * Loads the messages from database. + * You may override this method to customize the message storage in the database. + * @param string $category the message category + * @param string $language the target language + * @return array the messages loaded from database + * @since 1.1.5 + */ + protected function loadMessagesFromDb($category,$language) + { + $criteria = array('category'=>$category, "translations.language"=>$language); + $fields = array('message'=>true, 'translations.message'=>true); + $messages = $this->getCollection()->find($criteria, $fields); + + $result=array(); + foreach($messages as $message) + $result[$message['message']] = $message['translations'][0]['message']; + + return $result; + } +} \ No newline at end of file