Browse Source

test con l'andicappato

Paolo Libertini 6 years ago
commit
c29d0f5828

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/composer.lock
+/vendor
+/.idea

+ 40 - 0
composer.json

@@ -0,0 +1,40 @@
+{
+  "name": "moose/mongo-php-adapter",
+  "type": "library",
+  "description": "Adapter to provide mongodb interface",
+  "keywords": ["mongodb", "orm"],
+  "license": "MIT",
+  "authors": [
+    { "name": "Paolo Libertini", "email": "paolo.libertini@mooses.it" }
+  ],
+  "repositories": [
+    {
+      "type":"vcs",
+      "url": "https://topbucket.thinkopen.it/boarspring/repo_mongoPhp7.git"
+    },
+    {
+      "type":"vcs",
+      "url": "https://topbucket.thinkopen.it/boarspring/repo_zf1.git"
+    }
+  ],
+  "require": {
+    "php": "^7.1",
+    "ext-hash": "*",
+    "ext-curl": "*",
+    "ext-json": "*",
+    "psr/log": "1.1.*",
+    "monolog/monolog": "1.9.*",
+    "alcaeus/mongo-php-adapter": "^1.1",
+    "shardj/zendframework1-php7.2": "dev-master#release-1.13.1"
+  },
+  "autoload": {
+    "psr-0": {
+      "Moooses_": "lib/Mooses"
+    }
+  },
+  "extra": {
+    "branch-alias": {
+      "dev-master": "master"
+    }
+  }
+}

+ 351 - 0
lib/Mooses/AbstractMongo.php

@@ -0,0 +1,351 @@
+<?php
+
+/**
+ * Questa classe fa da interfaccia verso MongoDb
+ *
+ * @author Paolo Libertini <paolo.libertini@thinkopen.it>
+ */
+class Mooses_AbstractMongo extends Mooses_Mongodb_Mongo_Document {
+    const ASC = "ASC";
+    protected static $_db;
+    protected static $_collection;
+    protected static $_instance;
+    protected $calledClass;
+    protected $_classMethods = array();
+    protected static $_order = array();
+    protected $_queryConditions = array();
+    protected $_fields = array();
+    protected $_sorterArray = array();
+    protected $_sorterAttribute = "";
+    protected $_sorterDate = false;
+    protected $_sorterOrder = self::ASC;
+    protected $_forceNoCachePaginatedCollection = false;
+    public $loadKey = array();
+    protected $_dataUpdate = array();
+
+    public function __construct($_data = []) {
+        $this->_setCollectionAndDatabase();
+        parent::__construct($_data, array());
+        $this->calledClass = get_called_class();
+        self::$_instance = $this;
+        return $this;
+    }
+
+    private function _setCollectionAndDatabase(){
+        $_mongoInformations = $this->_retrieveTableName();
+        self::$_db = $_mongoInformations['db'];
+        static::$_db = $_mongoInformations['db'];
+        self::$_collection = $_mongoInformations['collection'];
+        static::$_collection = $_mongoInformations['collection'];
+        $this->_classMethods = get_class_methods($this);
+    }
+
+    public static function getInstance() {
+        if (self::$_instance === NULL || self::$_instance->calledClass !== get_called_class()) {
+            $_className = get_called_class();
+            self::$_instance = new $_className();
+        }
+        return self::$_instance;
+    }
+
+    protected function _retrieveTableName() {
+        $_return = array();
+        $annotations = new ReflectionClass($this);
+        $_tableSuffix = NULL;
+        $_tablePrefix = "re_";
+        $_databaseAnnotated = $this->_parseDocComment($annotations->getDocComment(), "@Db");
+        $_collectionAnnotated = $this->_parseDocComment($annotations->getDocComment(), "@Collection");
+        $_return['collection'] = (stristr($_collectionAnnotated, $_tableSuffix)) ? $_collectionAnnotated : $_tablePrefix . $_collectionAnnotated . $_tableSuffix;
+        $_return['db'] = $_databaseAnnotated;
+        return $_return;
+    }
+
+    private function _parseDocComment($str, $tag = '') {
+        if (empty($tag)) {
+            return $str;
+        }
+        $matches = array();
+        preg_match("/" . $tag . ":(.*)(\\r\\n|\\r|\\n)/U", $str, $matches);
+        if (isset($matches[1])) {
+            return trim($matches[1]);
+        }
+
+        return '';
+    }
+
+    protected function _convertMongoCursor($_mongoCursor, $_forceArray = false, $_order = false){
+        $_calledClass = $_mongoCursor->getDocumentClass();
+        if($_mongoCursor->count() == 0){
+            return (($_forceArray) ? array() : false);
+        } elseif($_mongoCursor->count() == 1){
+            $_object = (($_forceArray) ? array(new $_calledClass($_mongoCursor->next())) : new $_calledClass($_mongoCursor->next()));
+            $_object->loadKey = $this->loadKey;
+            return $_object;
+        } else {
+            $_arrayResults = array();
+            while($_result = $_mongoCursor->next()){
+                $_object = new $_calledClass($_result);
+                $_object->loadKey = $this->_loadKey;
+                array_push($_arrayResults, $_object);
+            }
+            if($_order !== false && is_array($_order)){
+                $this->_sorterAttribute = $_order[0];
+                $this->_sorterOrder = $_order[1];
+                $this->_sorterDate = (($_order[2] === true) ? true : false);
+                $_orderer = function($a, $b){
+                    if($this->_sorterDate === false) {
+                        if (strcasecmp((string)$a->getData($this->_sorterAttribute), (string)$b->getData($this->_sorterAttribute)) > 0) {
+                            return (($this->_sorterOrder == self::ASC) ? 1 : -1);
+                        } else {
+                            return (($this->_sorterOrder == self::ASC) ? -1 : 1);
+                        }
+                    } else {
+                        if(is_a($a, "Default_Model_Mapper_Utenti")) {
+                            $_datiA = $a->getAllData();
+                        } else {
+                            $_datiA = $a->getData();
+                        }
+                        if(is_array($_datiA[$this->_sorterAttribute]) && isset($_datiA[$this->_sorterAttribute]['date'])) {
+                            $_dataA = DateTime::createFromFormat("Y-m-d H:i:s", substr($_datiA[$this->_sorterAttribute]['date'], 0, 19));
+                        } else {
+                            $_dataA = DateTime::createFromFormat("Y-m-d H:i:s", substr($_datiA[$this->_sorterAttribute], 0, 19));
+                        }
+                        if(is_a($b, "Default_Model_Mapper_Utenti")) {
+                            $_datiB = $b->getAllData();
+                        } else {
+                            $_datiB = $b->getData();
+                        }
+                        if(is_array($_datiB[$this->_sorterAttribute]) && isset($_datiB[$this->_sorterAttribute]['date'])) {
+                            $_dataB = DateTime::createFromFormat("Y-m-d H:i:s", substr($_datiB[$this->_sorterAttribute]['date'], 0, 19));
+                        } else {
+                            $_dataB = DateTime::createFromFormat("Y-m-d H:i:s", substr($_datiB[$this->_sorterAttribute], 0, 19));
+                        }
+                        if($_dataA > $_dataB){
+                            return (($this->_sorterOrder == self::ASC) ? 1 : -1);
+                        } else {
+                            return (($this->_sorterOrder == self::ASC) ? -1 : 1);
+                        }
+                    }
+                };
+                usort($_arrayResults, $_orderer);
+            }
+            return $_arrayResults;
+        }
+    }
+
+    public function __callStatic($name, $arguments)
+    {
+        if(get_called_class() != get_class(self::$_instance)){
+            $_mongoInformations = $this->_retrieveTableName();
+            self::$_db = $_mongoInformations['db'];
+            static::$_db = $_mongoInformations['db'];
+            self::$_collection = $_mongoInformations['collection'];
+            static::$_collection = $_mongoInformations['collection'];
+        }
+    }
+
+    public function __call($methodName, $params = null) {
+        if(get_called_class() != get_class(self::$_instance)){
+            $this->_setCollectionAndDatabase();
+        }
+        $_methodsAllowed = array("setData", "setProperty", "getData", "getProperty");
+        $methodPrefix = substr($methodName, 0, 3);
+        $key = strtolower(substr($methodName, 3));
+        $_isUppercase = ctype_upper(substr($methodName, 3, 1));
+        if (in_array("___".$methodName, $_methodsAllowed) || in_array("___".$methodName, $this->_classMethods)) {
+            return call_user_func_array(array($this, "___".$methodName), $params);
+        } elseif ($methodPrefix == 'set' && count($params) == 1 && $_isUppercase) {
+            $value = htmlspecialchars($params[0],ENT_QUOTES,"UTF-8");
+            return parent::setProperty($key, $value);
+        } elseif ($methodPrefix == 'get') {
+            return htmlspecialchars(parent::getProperty($key),ENT_QUOTES);
+        } elseif (!in_array($methodName, array_flip(get_class_methods($this)))) {
+            throw new Exception("Method \"" . $methodName . "\" doesn't exist in " . get_called_class(), 500);
+        }
+    }
+
+    public static function addMongoRegexp(&$_value){
+        if(!is_array($_value) && stristr($_value, "/") != FALSE){
+            $_value = new MongoRegex($_value);
+        }
+    }
+
+    protected function ___getCollection(){
+        $this->_queryConditions = array();
+        return $this;
+    }
+
+    protected function ___addFieldToFilter($_attributeName, $_value){
+        if(!is_array($_value) && stristr($_value, "/") != FALSE){
+            $_value = new MongoRegex($_value);
+        }
+        if(is_array($_value)){
+            array_walk_recursive($_value, array(get_called_class(), "addMongoRegexp"));
+            $_condition = array();
+            foreach ($_value as $_operators => $_realValue) {
+                $_condition[$_operators] = $_realValue;
+            }
+        } else {
+            $_condition = $_value;
+        }
+        if(isset($this->_queryConditions[$_attributeName])){
+            $_alreadyExistingConditions = $this->_queryConditions[$_attributeName];
+            $_condition = array_merge($_alreadyExistingConditions, $_condition);
+        }
+        $this->_queryConditions[$_attributeName] = $_condition;
+        return $this;
+    }
+
+    protected function ___setFieldToSelect($_fields = array()){
+        $_truesFiller = function(&$_value){$_value = true;};
+        $_keysFields = array_flip($_fields);
+        array_walk_recursive($_keysFields, $_truesFiller);
+        $this->_fields = $_keysFields;
+        return $this;
+    }
+
+    protected function ___getCollectionData($_forceArray = true){
+        $_result = $this->fetchAll($this->_queryConditions, $this->_fields);
+        if (count($this->_sorterArray) == 0) {
+            return $this->_convertMongoCursor($_result, $_forceArray);
+        } else {
+            return $this->_convertMongoCursor($_result, $_forceArray, $this->_sorterArray);
+        }
+    }
+
+    protected function ___getPaginatedCollectionData($_page = 1, $_itemsPerPage = 15){
+        return $this->___getCollectionData(true);
+//        $_cacher = Mooses_Cacher::getInstance(strtolower("p_".str_replace("Default_Model_Mapper", "", get_called_class())), 3600);
+//        $_idCache = preg_replace("/[^a-zA-Z0-9_]+/", "", "query_" . json_encode($this->_queryConditions));
+//        if($this->_forceNoCachePaginatedCollection === true || !$_arrayResults = $_cacher->load($_idCache)) {
+//            $_result = $this->fetchAll($this->_queryConditions, $this->_fields);
+//            if (count($this->_sorterArray) == 0) {
+//                $_arrayResults = $this->_convertMongoCursor($_result, true);
+//            } else {
+//                $_arrayResults = $this->_convertMongoCursor($_result, true, $this->_sorterArray);
+//            }
+//            $_cacher->save($_arrayResults, $_idCache);
+//        }
+//        if(count($_arrayResults) > 0) {
+//            $_paginatorAdapter = new Zend_Paginator_Adapter_Array($_arrayResults);
+//            $_paginator = new Zend_Paginator($_paginatorAdapter);
+//            $_paginator->setItemCountPerPage($_itemsPerPage)->setCurrentPageNumber($_page);
+//            return $_paginator;
+//        } else {
+//            return false;
+//        }
+    }
+
+    protected function ___setCollectionOrder($_sorterArray){
+        $this->_sorterArray = $_sorterArray;
+        return $this;
+    }
+
+    protected function ___useCache($_useCache = true){
+        $this->_forceNoCachePaginatedCollection = !$_useCache;
+        return $this;
+    }
+
+    protected function ___getData($_key = NULL, $_default = NULL, $_forceDateExtendedFormat = false){
+        if(is_null($_key)) {
+            return parent::getAllData();
+        } else {
+            if(parent::hasProperty($_key)) {
+                $_value = parent::getProperty($_key);
+                if (is_object($_value)) {
+                    if(isset($_value->date)) {
+                        $_valueDatetime = DateTime::createFromFormat("Y-m-d H:i:s", $_value->date);
+                        if(is_object($_valueDatetime) && $_forceDateExtendedFormat){
+                            $_value = $_valueDatetime->format("d/m/Y H:i:s");
+                        } elseif(is_object($_valueDatetime)) {
+                            $_value = $_valueDatetime->format("d/m/Y");
+                        } else {
+                            $_value = $_value->date;
+                        }
+                    } else {
+                        $_value = $_value->getAllData();
+                    }
+                }
+                return $_value;
+            } else {
+                return (($_default != NULL) ? $_default : NULL);
+            }
+        }
+    }
+
+    protected function ___setData($_key, $_value = NULL, $_forceCleanData = false){
+        if(is_array($_key)){
+            foreach($_key as $_chiave => $_value){
+                parent::setProperty($_chiave, $_value);
+                $this->_dataUpdate[$_chiave] = $_value;
+            }
+        } else {
+            if($_key == "birthdate" || is_a($_value, "DateTime")){
+                $_oldValue = $_value;
+                $_value = new stdClass();
+                $_value->date = $_oldValue->format("Y-m-d H:i:s");
+                $_value->timezone_type = 3;
+                $_value->timezone = "Europe/Rome";
+            }
+            parent::setProperty($_key, $_value, $_forceCleanData);
+        }
+        $this->_dataUpdate[$_key] = $_value;
+        return $this;
+    }
+
+    protected function ___load($_value, $_attributeName = NULL){
+        if(is_null($_attributeName)){
+            return parent::find($_value);
+        } else {
+            return parent::fetchOne(array($_attributeName => $_value));
+        }
+    }
+
+    protected function ___loadByAttribute($_attributeName = "", $_value = "", $_forceArray = false){
+        if(stristr($_value, "/") !== FALSE){
+            $_value = new MongoRegex($_value);
+        }
+        $this->loadKey = array($_attributeName => $_value);
+        $_results = parent::fetchAll($this->loadKey);
+        return $this->_convertMongoCursor($_results, $_forceArray);
+    }
+
+    public function ___save($entierDocument = false, $safe = true){
+        return parent::save($entierDocument, $safe);
+    }
+
+    public function deleteData($_safe = true, $onlyOne = true){
+        $mongoCollection = $this->_getMongoCollection(true);
+        $this->preDelete();
+        if (!$this->isRootDocument()) {
+            $result = $mongoCollection->update($this->loadKey, array('$unset' => array($this->getPathToDocument() => 1)), array('safe' => $_safe));
+        }
+        else {
+            $result = $mongoCollection->remove($this->loadKey, array('justOne' => $onlyOne, 'safe' => $_safe));
+        }
+        return $result;
+    }
+
+    public function updateData(){
+        $_return = $this->update($this->loadKey, array('$set' => $this->_dataUpdate));
+        return $_return;
+    }
+
+    public function setUpdateCriteria($_array){
+        $this->loadKey = $_array;
+        return $this;
+    }
+
+    public function flatten($array, $prefix = '') {
+        $result = array();
+        foreach($array as $key=>$value) {
+            if(is_array($value)) {
+                $result = $result + $this->flatten($value, strtolower($prefix . $key . '_'));
+            }
+            else {
+                $result[strtolower($prefix . $key)] = $value;
+            }
+        }
+        return $result;
+    }
+}

+ 16 - 0
lib/Mooses/Mongodb/Auth/NoResultException.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: paolo
+ * Date: 09/12/15
+ * Time: 12:17
+ */
+class Mooses_Mongodb_Auth_NoResultException extends \Exception {
+    public function __construct($msg, $code)
+    {
+        $_logger = new Monolog\Logger('authexception');
+        $_logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__ . '/app.log', \Monolog\Logger::DEBUG));
+        $_logger->info("AUTH_NO_RESULT_EXC\t".$msg);
+        parent::__construct($msg, $code, NULL);
+    }
+}

+ 16 - 0
lib/Mooses/Mongodb/Auth/NonUniqueResultException.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: paolo
+ * Date: 09/12/15
+ * Time: 12:17
+ */
+class Mooses_Mongodb_Auth_NonUniqueResultException extends \Exception {
+    public function __construct($msg, $code)
+    {
+        $_logger = new Monolog\Logger('authexception');
+        $_logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__ . '/app.log', \Monolog\Logger::DEBUG));
+        $_logger->info("AUTH_NO_RESULT_EXC\t".$msg);
+        parent::__construct($msg, $code, NULL);
+    }
+}

+ 358 - 0
lib/Mooses/Mongodb/Mongo.php

@@ -0,0 +1,358 @@
+<?php
+
+require_once 'Thinkopen/Mongodb/Mongo/Validate/Array.php';
+require_once 'Thinkopen/Mongodb/Mongo/Validate/Class.php';
+require_once 'Thinkopen/Mongodb/Mongo/Validate/StubTrue.php';
+require_once 'Thinkopen/Mongodb/Mongo/Connection/Group.php';
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @license    New BSD License
+ * @author Paolo Libertini <paolo.libertini@thinkopen.it>
+ */
+class Mooses_Mongodb_Mongo {
+
+    protected static $_connectionGroups = array();
+    protected static $_requirements = array();
+    protected static $_requirementCreators = array();
+    protected static $_validOperations = array('$set', '$unset', '$push', '$pushAll', '$pull', '$pullAll', '$addToSet', '$pop', '$inc');
+    protected static $_initialised = false;
+
+    /**
+     * Initialise Mooses_Mongodb_Mongo. In particular all the requirements.
+     */
+    public static function init() {
+        // If requirements are not empty then we have already initialised requirements
+        if (static::$_initialised)
+            return;
+
+        // Custom validators
+        static::storeRequirement('Validator:Array', new Mooses_Mongodb_Mongo_Validate_Array());
+        static::storeRequirement('Validator:MongoId', new Mooses_Mongodb_Mongo_Validate_Class('MongoId'));
+        static::storeRequirement('Validator:MongoDate', new Mooses_Mongodb_Mongo_Validate_Class('MongoDate'));
+        static::storeRequirement('Document', new Mooses_Mongodb_Mongo_Validate_Class('Mooses_Mongodb_Mongo_Document'));
+        static::storeRequirement('DocumentSet', new Mooses_Mongodb_Mongo_Validate_Class('Mooses_Mongodb_Mongo_DocumentSet'));
+
+        // Stubs
+        static::storeRequirement('Required', new Mooses_Mongodb_Mongo_Validate_StubTrue());
+        static::storeRequirement('AsReference', new Mooses_Mongodb_Mongo_Validate_StubTrue());
+        static::storeRequirement('Ignore', new Mooses_Mongodb_Mongo_Validate_StubTrue());
+
+        // Requirement creator for validators
+        static::storeRequirementCreator('/^Validator:([A-Za-z]+[\w\-:]*)$/', function($data, $options = null) {
+            $instanceClass = $data[1];
+            if (!class_exists($instanceClass)) {
+                $instanceClass = 'Zend_Validate_' . $instanceClass;
+                if (!class_exists($instanceClass)) {
+                    return null;
+                }
+            }
+
+
+            if (!is_null($options))
+                $validator = new $instanceClass($options);
+            else
+                $validator = new $instanceClass();
+
+            if (!($validator instanceof Zend_Validate_Interface))
+                return null;
+
+            return $validator;
+        });
+
+        // Requirement creator for filters
+        static::storeRequirementCreator('/^Filter:([A-Za-z]+[\w\-:]*)$/', function($data, $options = null) {
+            $instanceClass = $data[1];
+            if (!class_exists($instanceClass)) {
+                $instanceClass = 'Zend_Filter_' . $instanceClass;
+                if (!class_exists($instanceClass)) {
+                    return null;
+                }
+            }
+
+            if (!is_null($options))
+                $validator = new $instanceClass($options);
+            else
+                $validator = new $instanceClass();
+
+            if (!($validator instanceof Zend_Filter_Interface))
+                return null;
+
+            return $validator;
+        });
+
+        // Creates requirements to match classes
+        $classValidator = function($data) {
+            if (!class_exists($data[1]))
+                return null;
+
+            return new Mooses_Mongodb_Mongo_Validate_Class($data[1]);
+        };
+
+        static::storeRequirementCreator('/^Document:([A-Za-z]+[\w\-]*)$/', $classValidator);
+        static::storeRequirementCreator('/^DocumentSet:([A-Za-z]+[\w\-]*)$/', $classValidator);
+
+        static::$_initialised = true;
+    }
+
+    /**
+     * Add connections Thinkopen Mongo
+     * 
+     * @param array $options
+     */
+    public static function addConnections($options) {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        }
+
+        $blurbs = array('host', 'master', 'masters', 'slaves', 'slave', 'hosts');
+        $intersection = array_intersect(array_keys($options), $blurbs);
+
+        $connectionGroups = array();
+        if (!empty($intersection))
+            $connectionGroups['default'] = $options;
+        else
+            $connectionGroups = $options;
+
+        foreach ($connectionGroups as $connectionGroupName => $connectionGroupOptions) {
+            static::getConnectionGroup($connectionGroupName)->addConnections($connectionGroupOptions);
+        }
+    }
+
+    /**
+     * Get the requirement matching the name provided
+     *
+     * @param $name String Name of requirement
+     * @return mixed
+     * */
+    public static function retrieveRequirement($name, $options = null) {
+        // Requirement is already initialised return it
+        if (isset(static::$_requirements[$name])) {
+            // If requirement does not have options, returned cached instance
+            if (!$options)
+                return static::$_requirements[$name];
+
+            $requirementClass = get_class(static::$_requirements[$name]);
+            return new $requirementClass($options);
+        }
+
+        // Attempt to create requirement
+        if (!$requirement = static::createRequirement($name, $options)) {
+            require_once 'Thinkopen/MongodbMongo/Exception.php';
+            throw new Mooses_Mongodb_Mongo_Exception("No requirement exists for '{$name}'");
+        }
+
+        // Requirement found. Store it for later use
+        if (!is_null($options)) {
+            static::storeRequirement($name, $requirement);
+        }
+
+        return $requirement;
+    }
+
+    /**
+     * Add requirements to use in validation of document properties
+     *
+     * @param $name String Name of requirement
+     * @param $requirement mixed
+     * */
+    public static function storeRequirement($name, $requirement) {
+        // Ensure $name is a string
+        $name = (string) $name;
+
+        static::$_requirements[$name] = $requirement;
+    }
+
+    /**
+     * Add a creator of requirements
+     *
+     * @param String Regex to match this requirement producer
+     * @param Closure Function to create requirement
+     * */
+    public static function storeRequirementCreator($regex, Closure $function) {
+        static::$_requirementCreators[$regex] = $function;
+    }
+
+    /**
+     * Create a requirement
+     *
+     * @param $name String Name of requirement
+     * @return mixed
+     * */
+    public static function createRequirement($name, $options = null) {
+        // Match requirement name against regex's
+        $requirements = array_reverse(static::$_requirementCreators);
+        foreach ($requirements as $regex => $function) {
+            $matches = array();
+            preg_match($regex, $name, $matches);
+
+            if (!empty($matches)) {
+                return $function($matches, $options);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Remove all requirements
+     */
+    public static function removeRequirements() {
+        static::$_requirements = array();
+    }
+
+    /**
+     * Remove all requirement creators
+     */
+    public static function removeRequirementCreators() {
+        static::$_requirementCreators = array();
+    }
+
+    /**
+     * Deterimine if an operation is valid
+     * 
+     * @param string $operation
+     */
+    public static function isValidOperation($operation) {
+        return in_array($operation, static::$_validOperations);
+    }
+
+    /**
+     * Determine if a connection group exists
+     * 
+     * @param string $name The name of the connection group
+     */
+    public static function hasConnectionGroup($name) {
+        return array_key_exists($name, static::$_connectionGroups);
+    }
+
+    /**
+     * Set a connection group
+     * 
+     * @param string $name
+     * @param Mooses_Mongodb_Mongo_Connection_Group $connectionGroup
+     */
+    public static function setConnectionGroup($name, Mooses_Mongodb_Mongo_Connection_Group $connectionGroup) {
+        static::$_connectionGroups[$name] = $connectionGroup;
+    }
+
+    /**
+     * Get a connection group. If it doesn't already exist, create it
+     * 
+     * @param string $name The name of the connection group
+     * @return Mooses_Mongodb_Mongo_Connection_Group
+     */
+    public static function getConnectionGroup($name) {
+        if (!static::hasConnectionGroup($name)) {
+            static::setConnectionGroup($name, new Mooses_Mongodb_Mongo_Connection_Group());
+        }
+
+        return static::$_connectionGroups[$name];
+    }
+
+    /**
+     * Get a list of all connection groups
+     * 
+     * @return array
+     */
+    public static function getConnectionGroups() {
+        return static::$_connectionGroups;
+    }
+
+    /**
+     * Remove all connection groups
+     */
+    public static function removeConnectionGroups() {
+        static::$_connectionGroups = array();
+    }
+
+    /**
+     * Add a connection to a master server
+     * 
+     * @param Mooses_Mongodb_Mongo_Connection $connection
+     * @param int $weight
+     */
+    public static function addMaster(Mooses_Mongodb_Mongo_Connection $connection, $weight = 1, $connectionGroup = 'default') {
+        static::getConnectionGroup($connectionGroup)->addMaster($connection, $weight);
+    }
+
+    /**
+     * Add a connection to a slaver server
+     * 
+     * @param $connection
+     * @param $weight
+     */
+    public static function addSlave(Mooses_Mongodb_Mongo_Connection $connection, $weight = 1, $connectionGroup = 'default') {
+        static::getConnectionGroup($connectionGroup)->addSlave($connection, $weight);
+    }
+
+    /**
+     * Get a write connection
+     * 
+     * @param string $connectionGroupName The connection group name
+     * @return Mooses_Mongodb_Mongo_Connection
+     */
+    public static function getWriteConnection($connectionGroupName = 'default') {
+        $connectionGroup = static::getConnectionGroup($connectionGroupName);
+
+        if ($connectionGroupName == 'default' && count($connectionGroup->getMasters()) === 0) {
+            // Add a connection to localhost if no connections currently exist for the default connection group
+            $_iniConfig = Top::getConfig();
+            if(strlen($_iniConfig->mongodb->host) > 0){
+                $_connectionHost = $_iniConfig->mongodb->host;
+            } else {
+                $_connectionHost = "127.0.0.1";
+            }
+            $connectionGroup->addMaster(new Mooses_Mongodb_Mongo_Connection($_connectionHost));
+        }
+
+        if (!$connection = $connectionGroup->getWriteConnection($connectionGroupName)) {
+            require_once 'Thinkopen/MongodbMongo/Exception.php';
+            throw new Mooses_Mongodb_Mongo_Exception("No write connection available for the '{$connectionGroupName}' connection group");
+        }
+
+        return $connection;
+    }
+
+    /**
+     * Get a read connection
+     * 
+     * @param string $connectionGroupName The connection group name
+     * @return Mooses_Mongodb_Mongo_Connection
+     */
+    public static function getReadConnection($connectionGroupName = 'default') {
+        $connectionGroup = static::getConnectionGroup($connectionGroupName);
+
+        if ($connectionGroupName == 'default' && count($connectionGroup->getSlaves()) === 0 && count($connectionGroup->getMasters()) === 0) {
+            // Add a connection to localhost if no connections currently exist for the default connection group
+            $_iniConfig = Top::getConfig();
+            if(strlen($_iniConfig->mongodb->host) > 0){
+                $_connectionHost = $_iniConfig->mongodb->host;
+            } else {
+                $_connectionHost = "127.0.0.1";
+            }
+            $connectionGroup->addMaster(new Mooses_Mongodb_Mongo_Connection($_connectionHost));
+        }
+
+        if (!$connection = $connectionGroup->getReadConnection($connectionGroupName)) {
+            require_once 'Thinkopen/MongodbMongo/Exception.php';
+            throw new Mooses_Mongodb_Mongo_Exception("No read connection available for the '{$connectionGroupName}' connection group");
+        }
+
+        return $connection;
+    }
+
+    /**
+     * Return Mooses_Mongodb_Mongo to pre-init status
+     */
+    public static function makeClean() {
+        static::removeConnectionGroups();
+        static::removeRequirements();
+        static::removeRequirementCreators();
+        static::$_initialised = false;
+    }
+
+}
+
+Mooses_Mongodb_Mongo::init();

+ 521 - 0
lib/Mooses/Mongodb/Mongo/Collection.php

@@ -0,0 +1,521 @@
+<?php
+
+require_once 'Thinkopen/Mongodb/Mongo/Document.php';
+
+require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+require_once 'Thinkopen/Mongodb/Mongo/Iterator/Cursor.php';
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+abstract class Mooses_Mongodb_Mongo_Collection
+{
+	protected static $_connectionGroup = 'default';
+	protected static $_db = null;
+	protected static $_collection = null;
+	protected static $_requirements = array();
+	protected static $_cachedCollectionInheritance = array();
+	protected static $_cachedCollectionRequirements = array();
+	protected static $_documentSetClass = 'Mooses_Mongodb_Mongo_DocumentSet';
+	
+	/**
+	 * Get the name of the mongo db
+	 * 
+	 * @return string
+	 */
+	public static function getDbName()
+	{
+		$db = static::$_db;
+
+		if (is_null($db)) {
+			$db = static::getConnection()->getDatabase();
+		}
+
+		return $db;
+	}
+	
+	/**
+	 * Get the name of the mongo collection
+	 * 
+	 * @return string
+	 */
+	public static function getCollectionName()
+	{
+		return static::$_collection;
+	}
+	
+	/**
+	 * Get the name of the connection group
+	 * 
+	 * @return string
+	 */
+	public static function getConnectionGroupName()
+	{
+		return static::$_connectionGroup;
+	}
+
+	/**
+	 * Determine if this collection has a database name set
+	 * 
+	 * @return boolean
+	 */
+	public static function hasDbName()
+	{
+		return !is_null(static::getDbName());
+	}
+	
+	/**
+	 * Determine if this collection has a collection name set
+	 * 
+	 * @return boolean
+	 */
+	public static function hasCollectionName()
+	{
+		return !is_null(static::getCollectionName());
+	}
+	
+	/**
+	 * Is this class a document class
+	 * 
+	 * @return boolean
+	 */
+	public static function isDocumentClass()
+	{
+		return is_subclass_of(get_called_class(), 'Mooses_Mongodb_Mongo_Document');
+	}
+	
+	/**
+	 * Get the name of the document class
+	 * 
+	 * @return string
+	 */
+	public static function getDocumentClass()
+	{
+		if (!static::isDocumentClass()) {
+			throw new Mooses_Mongodb_Mongo_Exception(get_called_class().' is not a document. Please extend Mooses_Mongodb_Mongo_Document');
+		}
+		
+		return get_called_class();
+	}
+	
+	/**
+	 * Get the name of the document set class
+	 * 
+	 * @return string
+	 */
+	public static function getDocumentSetClass()
+	{
+		return static::$_documentSetClass;
+	}
+	
+	/**
+	 * Get the inheritance of this collection
+	 */
+	public static function getCollectionInheritance()
+	{
+		$calledClass = get_called_class();
+		
+		// Have we already computed this collections inheritance?
+		if (array_key_exists($calledClass, static::$_cachedCollectionInheritance)) {
+			return static::$_cachedCollectionInheritance[$calledClass];
+		}
+		
+		$parentClass = get_parent_class($calledClass);
+		
+		if (is_null($parentClass::getCollectionName())) {
+			$inheritance = array($calledClass);
+		}
+		else {
+			$inheritance = $parentClass::getCollectionInheritance();
+			array_unshift($inheritance, $calledClass);
+		}
+		
+		static::$_cachedCollectionInheritance[$calledClass] = $inheritance;
+		return $inheritance;
+	}
+	
+	/**
+	 * Get requirements
+	 * 
+	 * @param bolean $inherited Include inherited requirements
+	 * @return array
+	 */
+	public static function getCollectionRequirements($inherited = true)
+	{
+		$calledClass = get_called_class();
+		
+		// Return if we only need direct requirements. ie no inherited requirements
+		if (!$inherited || $calledClass === __CLASS__) {
+			$reflector = new ReflectionProperty($calledClass, '_requirements');
+			if ($reflector->getDeclaringClass()->getName() !== $calledClass) return array();
+	
+			return static::makeRequirementsTidy($calledClass::$_requirements);
+		}
+		
+		// Have we already computed this collections requirements?
+		if (array_key_exists($calledClass, self::$_cachedCollectionRequirements)) {
+			return self::$_cachedCollectionRequirements[$calledClass];
+		}
+		
+		// Get parent collections requirements
+		$parentClass = get_parent_class($calledClass);
+		$parentRequirements = $parentClass::getCollectionRequirements();
+		
+		// Merge those requirements with this collections requirements
+		$requirements = static::mergeRequirements($parentRequirements, $calledClass::getCollectionRequirements(false));
+		self::$_cachedCollectionRequirements[$calledClass] = $requirements;
+		
+		return $requirements;
+	}
+	
+	/**
+	 * Process requirements to make sure they are in the correct format
+	 * 
+	 * @param array $requirements
+	 * @return array
+	 */
+	public static function makeRequirementsTidy(array $requirements) {
+		foreach ($requirements as $property => $requirementList) {
+			if (!is_array($requirementList)) {
+				$requirements[$property] = array($requirementList);
+			}
+				
+			$newRequirementList = array();
+			foreach ($requirements[$property] as $key => $requirement) {
+				if (is_numeric($key)) $newRequirementList[$requirement] = null;
+				else $newRequirementList[$key] = $requirement;
+			}
+			
+			$requirements[$property] = $newRequirementList;
+		}
+			
+		return $requirements;
+	}
+	
+	/**
+	 * Merge a two sets of requirements together
+	 * 
+	 * @param array $requirements
+	 * @return array
+	 */
+	public static function mergeRequirements($requirements1, $requirements2)
+	{
+		$requirements = $requirements1; 
+		
+		foreach ($requirements2 as $property => $requirementList) {
+			if (!array_key_exists($property, $requirements)) {
+				$requirements[$property] = $requirementList;
+				continue;
+			}
+			
+			foreach ($requirementList as $requirement => $options) {
+				// Find out if this is a Document or DocumentSet requirement
+				$matches = array();
+				preg_match("/^(Document|DocumentSet)(?::[A-Za-z][\w\-]*)?$/", $requirement, $matches);
+				
+				if (empty($matches)) {
+					$requirements[$property][$requirement] = $options;
+					continue;
+				}
+
+				// If requirement exists in existing requirements then unset it and replace it with the new requirements
+				foreach ($requirements[$property] as $innerRequirement => $innerOptions) {
+					$innerMatches = array();
+					
+					preg_match("/^{$matches[1]}(:[A-Za-z][\w\-]*)?/", $innerRequirement, $innerMatches);
+					
+					if (empty($innerMatches)) {
+						continue;
+					}
+					
+					unset($requirements[$property][$innerRequirement]);
+					$requirements[$property][$requirement] = $options;
+					break;
+				}
+			}
+		}
+		
+		return $requirements;
+	}
+
+	/*
+	 * Get a connection
+	 *
+	 * @param $writable should the connection be writable
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public static function getConnection($writable = true)
+	{
+		if ($writable) $connection = Mooses_Mongodb_Mongo::getWriteConnection(static::getConnectionGroupName());
+		else $connection = Mooses_Mongodb_Mongo::getReadConnection(static::getConnectionGroupName());
+
+		return $connection;
+	}
+
+	/**
+	 * Get an instance of MongoDb
+	 * 
+	 * @return MongoDb
+	 * @param boolean $useSlave
+	 */
+	public static function getMongoDb($writable = true)
+	{
+		if (!static::hasDbName()) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception(get_called_class().'::$_db is null');
+		}
+
+		return static::getConnection($writable)->selectDB(static::getDbName());
+	}
+	
+	/**
+	 * Get an instance of MongoCollection
+	 * 
+	 * @return MongoCollection
+	 * @param boolean $useSlave
+	 */
+	public static function getMongoCollection($writable = true)
+	{
+		if (!static::hasCollectionName()) {
+			throw new Mooses_Mongodb_Mongo_Exception(get_called_class().'::$_collection is null');
+		}
+		
+		return static::getMongoDb($writable)->selectCollection(static::getCollectionName());
+	}
+	
+	/**
+	 * Create a new document belonging to this collection
+	 * @param $data
+	 * @param boolean $new
+	 */
+	public static function create(array $data = array(), $new = true)
+	{
+		if (isset($data['_type']) && is_array($data['_type']) && class_exists($data['_type'][0]) && is_subclass_of($data['_type'][0], 'Mooses_Mongodb_Mongo_Document')) {
+			$documentClass = $data['_type'][0];
+		}
+		else {
+			$documentClass = static::getDocumentClass();
+		}
+		
+		$config = array();
+		$config['new'] = ($new);
+		$config['hasId'] = true;
+		$config['connectionGroup'] = static::getConnectionGroupName();
+		$config['db'] = static::getDbName();
+		$config['collection'] = static::getCollectionName();
+		return new $documentClass($data, $config);
+	}
+	
+	/**
+	 * Find a document by id
+	 * 
+	 * @param MongoId|String $id
+	 * @param array $fields
+	 * @return Mooses_Mongodb_Mongo_Document
+	 */
+	public static function find($id, array $fields = array())
+	{
+		if (!($id instanceof MongoId)) {
+			$id = new MongoId($id);
+		}
+		
+		$query = array('_id' => $id);
+		
+		return static::one($query, $fields);
+	}
+	
+	/**
+	 * Find one document
+	 * 
+	 * @param array $query
+	 * @param array $fields
+	 * @return Mooses_Mongodb_Mongo_Document
+	 */
+	public static function one(array $query = array(), array $fields = array())
+	{
+		$inheritance = static::getCollectionInheritance();
+		if (count($inheritance) > 1) {
+			$query['_type'] = $inheritance[0];
+		}
+
+		// If we are selecting specific fields make sure _type is always there
+		if (!empty($fields) && !isset($fields['_type'])) {
+			$fields['_type'] = 1;
+		}
+
+		$data = static::getMongoCollection(false)->findOne($query, $fields);
+		
+		if (is_null($data)) return null;
+		
+		return static::create($data, false);
+	}
+	
+	/** 
+	 * Find many documents
+	 * 
+	 * @param array $query
+	 * @param array $fields
+	 * @return Mooses_Mongodb_Mongo_Iterator_Cursor
+	 */
+	public static function all(array $query = array(), array $fields = array())
+	{
+		$inheritance = static::getCollectionInheritance();
+		if (count($inheritance) > 1) {
+			$query['_type'] = $inheritance[0];
+		}
+
+		// If we are selecting specific fields make sure _type is always there
+		if (!empty($fields) && !isset($fields['_type'])) {
+			$fields['_type'] = 1;
+		}
+		
+		$cursor = static::getMongoCollection(false)->find($query, $fields);
+
+		$config = array();
+		$config['connectionGroup'] = static::getConnectionGroupName();
+		$config['db'] = static::getDbName();
+		$config['collection'] = static::getCollectionName();
+		$config['documentClass'] = static::getDocumentClass();
+		$config['documentSetClass'] = static::getDocumentSetClass();
+
+		return new Mooses_Mongodb_Mongo_Iterator_Cursor($cursor, $config);
+	}
+
+	/**
+	 * Alias for one
+	 * 
+	 * @param array $query
+	 * @param array $fields
+	 * @return Mooses_Mongodb_Mongo_Document
+	 */
+	public static function fetchOne($query = array(), array $fields = array())
+	{
+		return static::one($query, $fields);
+	}
+	
+	/**
+	 * Alias for all
+	 * 
+	 * @param array $query
+	 * @param array $fields
+	 * @return Mooses_Mongodb_Mongo_Iterator_Cursor
+	 */
+	public static function fetchAll($query = array(), array $fields = array())
+	{
+		return static::all($query, $fields);
+	}
+	
+	/**
+	 * Select distinct values for a property
+	 * 
+	 * @param String $property
+	 * @return array
+	 */
+	public static function distinct($property, $query = array())
+	{
+		$results = static::getMongoDb(false)->command(array('distinct' => static::getCollectionName(), 'key' => $property, 'query' => $query));
+		
+		return $results['values'];
+	}
+	
+	/**
+	 * Insert a document
+	 * 
+	 * @param array $document
+	 * @param array $options
+	 */
+	public static function insert(array $document, array $options = array())
+	{
+		return static::getMongoCollection(true)->insert($document, $options);
+	}
+
+	/**
+	 * Insert a batch of documents
+	 *
+	 * @param array $documents
+	 * @param unknown_type $options
+	 */
+	public static function insertBatch(array $documents, array $options = array())
+	{
+		return static::getMongoCollection(true)->batchInsert($documents, $options);
+	}
+	
+	/**
+	 * Update documents from this collection
+	 * 
+	 * @param $criteria
+	 * @param $object
+	 * @param $options
+	 */
+	public static function update(array $criteria, array $object, array $options = array())
+	{
+		return static::getMongoCollection(true)->update($criteria, $object, $options);
+	}
+	
+	/**
+	 * Remove documents from this collection
+	 * 
+	 * @param array $criteria
+	 * @param unknown_type $justone
+	 */
+	public static function remove(array $criteria, array $options = array())
+	{
+		// if you want to remove a document by MongoId
+	        if (array_key_exists('_id', $criteria) && !($criteria["_id"] instanceof MongoId)) {
+	            $criteria["_id"] = new MongoId($criteria["_id"]);
+	        }
+	        
+		return static::getMongoCollection(true)->remove($criteria, $options);
+	}
+	
+	/**
+	 * Drop this collection
+	 */
+	public static function drop()
+	{
+		return static::getMongoCollection(true)->drop();
+	}
+	
+	/**
+	 * Ensure an index 
+	 * 
+	 * @param array $keys
+	 * @param array $options
+	 */
+	public static function ensureIndex(array $keys, $options = array())
+	{
+		return static::getMongoCollection(true)->ensureIndex($keys, $options);
+	}
+	
+	/**
+	 * Delete an index
+	 * 
+	 * @param string|array $keys
+	 */
+	public static function deleteIndex($keys)
+	{
+		return static::getMongoCollection(true)->deleteIndex($keys);
+	}
+	
+	/**
+	 * Remove all indexes from this collection
+	 */
+	public static function deleteIndexes()
+	{
+		return static::getMongoCollection(true)->deleteIndexes();
+	}
+	
+	/**
+	 * Get index information for this collection
+	 * 
+	 * @return array
+	 */
+	public static function getIndexInfo()
+	{
+		return static::getMongoCollection(false)->getIndexInfo();
+	}
+}

+ 164 - 0
lib/Mooses/Mongodb/Mongo/Connection.php

@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+class Mooses_Mongodb_Mongo_Connection extends MongoClient
+{
+	static protected $_availableOptions = array(
+		'persist',
+		'connectTimeoutMS',
+		'replicaSet'
+	);
+	
+	protected $_connectionInfo = array();
+	
+	public function __construct($connectionString = null, array $options = array())
+	{
+		Mooses_Mongodb_Mongo::init();
+		
+		// Set the server to local host if one was not provided
+        $_iniConfig = Top::getConfig();
+        if(strlen($_iniConfig->mongodb->host) > 0){
+            $_connectionHost = $_iniConfig->mongodb->host;
+        } else {
+            $_connectionHost = "127.0.0.1";
+        }
+		if (is_null($connectionString)) $connectionString = $_connectionHost;
+
+		// Force mongo to connect only when we need to
+		$options['connect'] = false;
+		$connectionInfo = self::parseConnectionString($connectionString);
+		
+		$this->_connectionInfo = array_merge($options, $connectionInfo);
+
+		return parent::__construct($connectionString, $options);
+	}
+
+	/**
+	 * Get some info about this connection
+	 *
+	 * @return array
+	 */
+	public function getConnectionInfo()
+	{
+		return $this->_connectionInfo;
+	}
+
+	/**
+	 * Get the actual connection string used for this connection. This differs from __toString in
+	 * that __toString returns a string representation of the connection, not the connection string used
+	 *
+	 * @return array
+	 */
+	public function getActualConnectionString()
+	{
+		return $this->_connectionInfo['connectionString'];
+	}
+	
+	/**
+	 * Get the database this connection is connection to
+	 *
+	 * @return string
+	 */
+	public function getDatabase()
+	{
+		if (!isset($this->_connectionInfo['database'])) return null;
+
+		return $this->_connectionInfo['database'];
+	}
+
+	/**
+	 * Get a list of the hosts this connection is connection to
+	 *
+	 * @return array
+	 */
+	public function getHosts()
+	{
+		return $this->_connectionInfo['hosts'];
+	}
+
+	/**
+	 * Parse the connection string
+	 *
+	 * @param  $connectionString
+	 * @return array
+	 */
+	public static function parseConnectionString($connectionString)
+	{
+		$connectionInfo = array(
+			'connectionString' => $connectionString
+		);
+
+		// Remove mongodb protocol string
+		if (substr($connectionString, 0, 10) == 'mongodb://') {
+			$connectionString = substr($connectionString, 10);
+		}
+
+		// Is there a database name
+		if ($pos = strrpos($connectionString, '/')) {
+			$connectionInfo['database'] = substr($connectionString, $pos+1);
+			$connectionString = substr($connectionString, 0, $pos);
+		}
+
+		// Fetch Hosts
+		if (!empty($connectionString)) {
+			$hostStrings = explode(',', $connectionString);
+			$connectionInfo['hosts'] = array();
+			foreach ($hostStrings as $hostString) {
+				$connectionInfo['hosts'][] = self::parseHostString($hostString);
+			}
+		}
+		
+		return $connectionInfo;
+	}
+
+	/**
+	 * Parse a host string
+	 *
+	 * @param  $hostString
+	 * @return array
+	 */
+	public static function parseHostString($hostString)
+	{
+		$hostInfo = array();
+
+		// Are we authenticating with a username and password?
+		if ($pos = strpos($hostString, '@')) {
+			$authString = substr($hostString, 0, $pos);
+
+			$data = explode(':', $authString);
+			$hostInfo['username'] = $data[0];
+
+			if (count($data) > 1) {
+				$hostInfo['password'] = $data[1];
+			}
+			
+			$hostString = substr($hostString, $pos+1);
+		}
+
+		// Grab host and port
+		$data = explode(':', $hostString);
+		$hostInfo['host'] = $data[0];
+
+		if (count($data) > 1) {
+			$hostInfo['port'] = $data[1];
+		}
+
+		return $hostInfo;
+	}
+
+	/**
+	 * Get available options
+	 * 
+	 * @return array
+	 */
+	static public function getAvailableOptions()
+	{
+		return static::$_availableOptions;
+	}
+}

+ 225 - 0
lib/Mooses/Mongodb/Mongo/Connection/Group.php

@@ -0,0 +1,225 @@
+<?php
+
+require_once 'Thinkopen/Mongodb/Mongo/Connection.php';
+require_once 'Thinkopen/Mongodb/Mongo/Connection/Stack.php';
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+class Mooses_Mongodb_Mongo_Connection_Group
+{
+	protected $_masters = null;
+	protected $_slaves = null;
+	
+	public function __construct(array $connectionOptions = null)
+	{
+		$this->_masters = new Mooses_Mongodb_Mongo_Connection_Stack();
+		$this->_slaves = new Mooses_Mongodb_Mongo_Connection_Stack();
+		
+		// add connections
+		if (!is_null($connectionOptions)) {
+			$this->addConnections($connectionOptions);
+		}
+	}
+	
+	/**
+	 * Add multiple connections at once using arrays of options
+	 * 
+	 * @param array $connectionOptions
+	 */
+	public function addConnections($connectionOptions)
+	{
+		if ($connectionOptions instanceof Zend_Config) {
+			$connectionOptions = $connectionOptions->toArray();
+		}
+		
+		$masters = array();
+		$masterStackOptions = array();
+		$slaves = array();
+		$slaveStackOptions = array();
+		
+		$group = $this;
+		$addConnections = function(Mooses_Mongodb_Mongo_Connection_Stack $stack, array $connections) use ($group) {
+			foreach ($connections as $connectionData) {
+				$options = array_intersect_key($connectionData, array_flip(Mooses_Mongodb_Mongo_Connection::getAvailableOptions()));
+				
+				$connection = new Mooses_Mongodb_Mongo_Connection($group->formatConnectionString($connectionData), $options);
+				if (array_key_exists('weight', $connectionData)) $weight = (int) $connectionData['weight'];
+				else $weight = 1;
+				
+				$stack->addNode($connection, $weight);
+			}
+		};
+		
+		// Lets add our masters
+		if (array_key_exists('master', $connectionOptions)) $masters[] = $connectionOptions['master']; // single master
+		elseif (array_key_exists('masters', $connectionOptions)) {
+			$connectionKeys = array_filter(array_keys($connectionOptions['masters']), 'is_numeric');
+			$masters = array_intersect_key($connectionOptions['masters'], array_flip($connectionKeys)); // only connections
+			$masterStackOptions = array_diff_key($connectionOptions['masters'], array_flip($connectionKeys)); // only options
+		}
+		else $masters[] = $connectionOptions; // one server
+		
+		$addConnections($this->getMasters(), $masters); // Add master connections
+		$this->getMasters()->setOptions($masterStackOptions); // Set master stack options
+		
+		// Lets add our slaves
+		if (array_key_exists('slave', $connectionOptions)) $slaves[] = $connectionOptions['slave']; // single slave
+		elseif (array_key_exists('slaves', $connectionOptions)) {
+			$connectionKeys = array_filter(array_keys($connectionOptions['slaves']), 'is_numeric');
+			$slaves = array_intersect_key($connectionOptions['slaves'], array_flip($connectionKeys)); // only connections
+			$slaveStackOptions = array_diff_key($connectionOptions['slaves'], array_flip($connectionKeys)); // only options
+		}; 
+		
+		$addConnections($this->getSlaves(), $slaves); // Add slave connections
+		$this->getSlaves()->setOptions($slaveStackOptions); // Set slave stack options
+	}
+	
+	/**
+	 * Add a connection to a master server
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Connection $connection
+	 * @param int $weight
+	 */
+	public function addMaster(Mooses_Mongodb_Mongo_Connection $connection, $weight = 1)
+	{
+		$this->_masters->addNode($connection, $weight);
+	}
+	
+	/**
+	 * Get all master connections
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection_Stack
+	 */
+	public function getMasters()
+	{
+		return $this->_masters;
+	}
+	
+	/**
+	 * Add a connection to a slaver server
+	 * 
+	 * @param $connection
+	 * @param $weight
+	 */
+	public function addSlave(Mooses_Mongodb_Mongo_Connection $connection, $weight = 1)
+	{
+		$this->_slaves->addNode($connection, $weight);
+	}
+	
+	/**
+	 * Get all slave connections
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection_Stack
+	 */
+	public function getSlaves()
+	{
+		return $this->_slaves;
+	}
+	
+	/**
+	 * Get a write connection
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public function getWriteConnection()
+	{
+		// Select master
+		$write = $this->_masters->selectNode();
+		if ($write && !$write->connected) {
+                    $write->connect();
+                }
+		
+		return $write;
+	}
+	
+	/**
+	 * Get a read connection
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public function getReadConnection()
+	{
+		if (count($this->_slaves) === 0) {
+			// If no slaves then get a master connection
+			$read = $this->getWriteConnection();
+		}
+		else {
+			// Select slave
+			$read = $this->_slaves->selectNode();
+			if ($read) $read->connect();
+		}
+		
+		return $read;
+	}
+	
+	/**
+	 * Format a connection string
+	 * 
+	 * @param array $connectionOptions
+	 * 
+	 */
+	public function formatConnectionString(array $connectionOptions = array())
+	{
+		// See if we are dealing with a replica set
+		if (array_key_exists('hosts', $connectionOptions)) $hosts = $connectionOptions['hosts'];
+		else $hosts = array($connectionOptions);
+		
+		$connectionString = 'mongodb://';
+		
+		$hostStringList = array();
+		foreach ($hosts as $hostOptions) {
+			$hostStringList[] = static::formatHostString($hostOptions);
+		}
+		
+		$connectionString .= implode(',', $hostStringList);
+
+		// Set database
+		if (isset($connectionOptions['database'])) $connectionString .= '/'.$connectionOptions['database'];
+
+		return $connectionString;
+	}
+	
+	/**
+	 * Format a host string
+	 * 
+	 * @param $options
+	 * @return string
+	 */
+	public function formatHostString(array $hostOptions = array())
+	{
+		$hostString = '';
+		
+		// Set username
+		if (isset($hostOptions['username']) && !is_null($hostOptions['username'])) {
+			$hostString .= $hostOptions['username'];
+			
+			// Set password
+			if (isset($hostOptions['password']) && !is_null($hostOptions['password'])) {
+				$hostString .= ':'.$hostOptions['password'];
+			}
+			
+			$hostString .= '@';
+		}
+		$_iniConfig = Top::getConfig();
+		if(strlen($_iniConfig->mongodb->host) > 0){
+			$_connectionHost = $_iniConfig->mongodb->host;
+		} else {
+			$_connectionHost = "127.0.0.1";
+		}
+		// Set host
+		if (isset($hostOptions['host']) && !is_null($hostOptions['host'])) $hostString .= $hostOptions['host'];
+		else $hostString .= $_connectionHost;
+		
+		// Set port
+		$hostString .= ':';
+		if (isset($hostOptions['port']) && !is_null($hostOptions['port'])) $hostString .= $hostOptions['port'];
+		else $hostString .= '27017';
+		
+		return $hostString;
+	}
+}

+ 258 - 0
lib/Mooses/Mongodb/Mongo/Connection/Stack.php

@@ -0,0 +1,258 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+class Mooses_Mongodb_Mongo_Connection_Stack implements SeekableIterator, Countable, ArrayAccess
+{
+	protected $_position = 0;
+	protected $_nodes = array();
+	protected $_weights = array();
+	protected $_options = array(
+		'cacheConnectionSelection' => true
+	);
+	protected $_cacheConnectionSelection = true;
+	protected $_cachedConnection = null;
+	
+	/**
+	 * Get an option
+	 * 
+	 * @param string $option
+	 */
+	public function getOption($option)
+	{
+		if (!array_key_exists($option, $this->_options)) {
+			return null;
+		}
+		
+		return $this->_options[$option];
+	}
+	
+	/**
+	 * Set an option
+	 * 
+	 * @param string $option
+	 * @param mixed $value
+	 */
+	public function setOption($option, $value)
+	{
+		$this->_options[$option] = $value;
+	}
+	
+	/**
+	 * Set Options
+	 * 
+	 * @param array $options
+	 */
+	public function setOptions(array $options)
+	{
+		$this->_options = array_merge($this->_options, $options);
+	}
+	
+	/**
+	 * Add node to connection stack
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Connection $connection
+	 * @param int $weight
+	 */
+	public function addNode(Mooses_Mongodb_Mongo_Connection $connection, $weight = 1)
+	{
+		$this->_nodes[] = $connection;
+		$this->_weights[] = (int) $weight;
+	}
+	
+	/**
+	 * Select a node from the connection stack. 
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public function selectNode()
+	{
+		if (count($this) == 0) {
+			// no nodes to select from
+			return null;
+		}
+		
+		// Return the cached connection if available
+		if ($this->cacheConnectionSelection() && $this->hasCachedConnection()) {
+			return $this->getCachedConnection();
+		}
+		
+		// Select a new connection
+		$r = mt_rand(1,array_sum($this->_weights));
+		$offset = 0;
+		foreach ($this->_weights as $k => $weight) {
+			$offset += $weight;
+			if ($r <= $offset) {
+				$connection = $this->_nodes[$k];
+				break;
+			}
+		}
+		
+		// Cache the connection for later use
+		if ($this->cacheConnectionSelection()) {
+			$this->_cachedConnection = $connection;
+		}
+		
+		return $connection;
+	}
+	
+	/**
+	 * Determine if this connection stack has a cached connection
+	 * 
+	 * @return boolean
+	 */
+	public function hasCachedConnection()
+	{
+		return !is_null($this->_cachedConnection);
+	}
+	
+	/**
+	 * Get the cached connection
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public function getCachedConnection()
+	{
+		return $this->_cachedConnection;
+	}
+	
+	/**
+	 * Get or set the flag to determine if the first connection selection should be cached
+	 * 
+	 * @param boolean $value
+	 */
+	public function cacheConnectionSelection($value = null)
+	{
+		if (!is_null($value)) {
+			$this->_options['cacheConnectionSelection'] = (boolean) $value;
+		}
+		
+		return $this->_options['cacheConnectionSelection'];
+	}
+	
+	/**
+	 * Seek to a particular connection
+	 * 
+	 * @param $position
+	 */
+	public function seek($position)
+	{
+		if (!is_numeric($position)) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception("Position must be numeric");
+		}
+		
+		$this->_position = $position;
+		
+		if (!$this->valid()) {
+			throw new OutOfBoundsException("invalid seek position ($position)");
+		}
+	}
+	
+	/**
+	 * Get the current connection
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Connection
+	 */
+	public function current()
+	{
+		return $this->_nodes[$this->_position];
+	}
+	
+	/**
+	 * Get teh current key
+	 * 
+	 * @return int
+	 */
+	public function key()
+	{
+		return $this->_position;
+	}
+	
+	/**
+	 * Move the pointer to the next connection
+	 */
+	public function next()
+	{
+		$this->_position +=1;
+	}
+	
+	/**
+	 * Rewind the pointer to the begining of the stack
+	 */
+	public function rewind()
+	{
+		$this->_position = 0;
+	}
+	
+	/**
+	 * Is the location of the current pointer valid
+	 */
+	public function valid()
+	{
+		return $this->offsetExists($this->_position);
+	}
+	
+	/**
+	 * Count all the connections
+	 */
+	public function count()
+	{
+		return count($this->_nodes);
+	}
+	
+	/**
+	 * Test if an offset exists
+	 * 
+	 * @param int $offset
+	 */
+	public function offsetExists($offset)
+	{
+		return array_key_exists($offset, $this->_nodes);
+	}
+	
+	/**
+	 * Get an offset
+	 * 
+	 * @param int $offset
+	 */
+	public function offsetGet($offset)
+	{
+		if (!$this->offsetExists($offset)) return null;
+		
+		return $this->_nodes[$offset];
+	}
+	
+	/**
+	 * Set an offset
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Connection $offset
+	 * @param $connection
+	 */
+	public function offsetSet($offset, $connection)
+	{
+		if (!is_numeric($offset)) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception("Offset must be numeric");
+		}
+		
+		$this->_nodes[$offset] = $connection;
+		$this->_weights[$offset] = 1;
+	}
+	
+	/**
+	 * Unset an offset
+	 * 
+	 * @param int $offset
+	 */
+	public function offsetUnset($offset)
+	{
+		unset($this->_nodes[$offset]);
+		unset($this->_weights[$offset]);
+	}
+}

+ 1366 - 0
lib/Mooses/Mongodb/Mongo/Document.php

@@ -0,0 +1,1366 @@
+<?php
+//require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+//require_once 'Thinkopen/Mongodb/Mongo/Collection.php';
+//require_once 'Thinkopen/Mongodb/Mongo/Iterator/Default.php';
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License 
+ */
+class Mooses_Mongodb_Mongo_Document extends Mooses_Mongodb_Mongo_Collection implements ArrayAccess, Countable, IteratorAggregate
+{
+	protected static $_requirements = array(
+		'_id' => 'Validator:MongoId',
+		'_type' => 'Array'
+	);
+	
+	protected $_docRequirements = array();
+	protected $_filters = array();
+	protected $_validators = array();
+	protected $_data = array();
+	protected $_cleanData = array();
+	protected $_config = array(
+		'new' => true,
+		'connectionGroup' => null,
+		'db' => null,
+		'collection' => null,
+		'pathToDocument' => null,
+		'criteria' => array(),
+		'parentIsDocumentSet' => false,
+		'requirementModifiers' => array(),
+		'locked' => false
+	);
+	protected $_operations = array();
+	protected $_references = null;
+	
+	public function __construct($data = array(), $config = array())
+	{
+		// Make sure mongo is initialised
+		Mooses_Mongodb_Mongo::init();
+		
+		$this->_config = array_merge($this->_config, $config);
+		$this->_references = new SplObjectStorage();
+
+		// If not connected and this is a new root document, figure out the db and collection
+		if ($this->isNewDocument() && $this->isRootDocument() && !$this->isConnected()) {
+			$this->setConfigAttribute('connectionGroup', static::getConnectionGroupName());
+			$this->setConfigAttribute('db', static::getDbName());
+			$this->setConfigAttribute('collection', static::getCollectionName());
+		}
+		
+		// Get collection requirements
+		$this->_docRequirements = static::getCollectionRequirements();
+		
+		// apply requirements requirement modifiers
+		$this->applyRequirements($this->_config['requirementModifiers'], false);
+
+		// Store data
+		$this->_cleanData = $data;
+
+		// Initialize input data
+		if ($this->isNewDocument() && is_array($data)) {
+			foreach ($data as $key => $value) {
+				$this->getProperty($key);
+			}
+		}
+
+		// Create document id if one is required
+		if ($this->isNewDocument() && ($this->hasKey() || (isset($this->_config['hasId']) && $this->_config['hasId']))) {
+			$this->_data['_id'] = new MongoId();
+			$this->_data['_type'] = static::getCollectionInheritance();
+		}
+		
+		// If has key then add it to the update criteria
+		if ($this->hasKey()) {
+			$this->setCriteria($this->getPathToProperty('_id'), $this->getId());
+		}
+		
+		$this->init();
+	}
+	
+	protected function init()
+	{
+		
+	}
+	
+	protected function preInsert()
+	{
+		
+	}
+	
+	protected function postInsert()
+	{
+		
+	}
+	
+	protected function preUpdate()
+	{
+		
+	}
+	
+	protected function postUpdate()
+	{
+		
+	}
+	
+	protected function preSave()
+	{
+		
+	}
+	
+	protected function postSave()
+	{
+		
+	}
+	
+	protected function preDelete()
+	{
+		
+	}
+	
+	protected function postDelete()
+	{
+		
+	}
+	
+	/**
+	 * Get this document's id
+	 * 
+	 * @return MongoId
+	 */
+	public function getId()
+	{
+		return $this->_id;
+	}
+	
+	/**
+	 * Set this document's id
+	 * 
+	 * @return MongoId
+	 */
+	public function setId(MongoId $id)
+	{
+		$this->_id = $id;
+		$this->setConfigAttribute('new', false);
+		$this->setCriteria($this->getPathToProperty('_id'), $id);
+	}
+	
+	/**
+	 * Does this document have an id
+	 * 
+	 * @return boolean
+	 */
+	public function hasId()
+	{
+		return !is_null($this->getId());
+	}
+	
+	/**
+	 * Get the inheritance of this document
+	 * 
+	 * @return array
+	 */
+	public function getInheritance()
+	{
+		return $this->_type;
+	}
+	
+	/**
+	 * Get a config attribute
+	 * 
+	 * @param string $attribute
+	 */
+	public function getConfigAttribute($attribute)
+	{
+		if (!$this->hasConfigAttribute($attribute)) return null;
+		
+		return $this->_config[$attribute];
+	}
+	
+	/**
+	 * Set a config attribute
+	 * 
+	 * @param string $attribute
+	 * @param unknown_type $value
+	 */
+	public function setConfigAttribute($attribute, $value)
+	{
+		$this->_config[$attribute] = $value;
+	}
+	
+	/**
+	 * Determine if a config attribute is set
+	 * 
+	 * @param string $attribute
+	 */
+	public function hasConfigAttribute($attribute)
+	{
+		return array_key_exists($attribute, $this->_config);
+	}
+	
+	/**
+	 * Is this document connected to a db and collection
+	 */
+	public function isConnected()
+	{
+		return (!is_null($this->getConfigAttribute('connectionGroup')) && !is_null($this->getConfigAttribute('db')) && !is_null($this->getConfigAttribute('collection')));
+	}
+	
+	/**
+	 * Is this document locked
+	 * 
+	 * @return boolean
+	 */
+	public function isLocked()
+	{
+		return $this->getConfigAttribute('locked');
+	}
+	
+	/**
+	 * Get the path to this document from the root document
+	 * 
+	 * @return string
+	 */
+	public function getPathToDocument()
+	{
+		return $this->getConfigAttribute('pathToDocument');
+	}
+	
+	/**
+	 * Set the path to this document from the root document
+	 * @param unknown_type $path
+	 */
+	public function setPathToDocument($path)
+	{
+		$this->setConfigAttribute('pathToDocument', $path);
+	}
+	
+	/**
+	 * Get the full path from the root document to a property
+	 * 
+	 * @param $property
+	 * @return string
+	 */
+	public function getPathToProperty($property)
+	{
+		if ($this->isRootDocument()) return $property;
+		
+		return $this->getPathToDocument().'.'.$property;
+	}
+
+	/**
+	 * Is this document a root document
+	 * 
+	 * @return boolean
+	 */
+	public function isRootDocument()
+	{
+		return is_null($this->getPathToDocument());
+	}
+	
+/**
+	 * Determine if this document has a key
+	 * 
+	 * @return boolean
+	 */
+	public function hasKey()
+	{
+		return ($this->isRootDocument() && $this->isConnected());
+	}
+	
+	/**
+	 * Is this document a child element of a document set
+	 * 
+	 * @return boolean
+	 */
+	public function isParentDocumentSet()
+	{
+		return $this->_config['parentIsDocumentSet'];
+	}
+	
+	/**
+	 * Determine if the document has certain criteria
+	 * 
+	 * @return boolean
+	 */
+	public function hasCriteria($property)
+	{
+		return array_key_exists($property, $this->_config['criteria']);
+	}
+	
+	/**
+	 * Add criteria
+	 * 
+	 * @param string $property
+	 * @param MongoId $id
+	 */
+	public function setCriteria($property = null, $value = null)
+	{
+		$this->_config['criteria'][$property] = $value;
+	}
+	
+	/**
+	 * Get criteria
+	 * 
+	 * @param string $property
+	 * @return mixed
+	 */
+	public function getCriteria($property = null)
+	{
+		if (is_null($property)) return $this->_config['criteria'];
+		
+		if (!array_key_exists($property, $this->_config['criteria'])) return null;
+		
+		return $this->_config['criteria'][$property];
+	}
+	
+	/**
+	 * Fetch an instance of MongoDb
+	 * 
+	 * @param boolean $writable
+	 * @return MongoDb
+	 */
+	public function _getMongoDb($writable = true)
+	{
+		if (is_null($this->getConfigAttribute('db'))) {
+			throw new Mooses_Mongodb_Mongo_Exception('Can not fetch instance of MongoDb. Document is not connected to a db.');
+		}
+		
+		if ($writable) $connection = Mooses_Mongodb_Mongo::getWriteConnection($this->getConfigAttribute('connectionGroup'));
+		else $connection = Mooses_Mongodb_Mongo::getReadConnection($this->getConfigAttribute('connectionGroup'));
+		
+		$temp = $connection->selectDB($this->getConfigAttribute('db'));
+
+		# Tells replica set how many nodes must have the data before success
+//		$temp->w = 2;
+		
+		return $temp;
+	}
+	
+	/**
+	 * Fetch an instance of MongoCollection
+	 * 
+	 * @param boolean $writable
+	 * @return MongoCollection
+	 */
+	public function _getMongoCollection($writable = true)
+	{
+		if (is_null($this->getConfigAttribute('collection'))) {
+			throw new Mooses_Mongodb_Mongo_Exception('Can not fetch instance of MongoCollection. Document is not connected to a collection.');
+		}
+		
+		return $this->_getMongoDb($writable)->selectCollection($this->getConfigAttribute('collection'));
+	}
+
+	/**
+	 * Apply a set of requirements
+	 * 
+	 * @param array $requirements
+	 */
+	public function applyRequirements($requirements, $dirty = true)
+	{
+		if ($dirty) {
+			$requirements = static::makeRequirementsTidy($requirements);
+		}
+		
+		$this->_docRequirements = static::mergeRequirements($this->_docRequirements, $requirements);
+		$this->_filters = null;
+		$this->_validators = null;
+	}
+	
+	/**
+	 * Test if this document has a particular requirement
+	 * 
+	 * @param string $property
+	 * @param string $requirement
+	 */
+	public function hasRequirement($property, $requirement)
+	{
+		if (!array_key_exists($property, $this->_docRequirements)) return false;
+		
+		switch($requirement) {
+			case 'Document':
+			case 'DocumentSet':
+				foreach ($this->_docRequirements[$property] as $requirementSearch => $params) {
+					$standardClass = 'Mooses_Mongodb_Mongo_'.$requirement;
+					
+					// Return basic document or document set class if requirement matches
+					if ($requirementSearch == $requirement) {
+						return $standardClass;
+					}
+					
+					// Find the document class
+					$matches = array();
+					preg_match("/^{$requirement}:([A-Za-z][\w\-]*)$/", $requirementSearch, $matches);
+					
+					if (!empty($matches)) {
+						if (!class_exists($matches[1])) {
+							throw new Mooses_Mongodb_Mongo_Exception("$requirement class of '{$matches[1]}' does not exist");
+						}
+						
+						if (!is_subclass_of($matches[1], $standardClass)) {
+							throw new Mooses_Mongodb_Mongo_Exception("$requirement of '{$matches[1]}' sub is not a class of $standardClass does not exist");
+						}
+						
+						return $matches[1];
+					}
+				}
+				
+				return false;
+		}
+		
+		return array_key_exists($requirement, $this->_docRequirements[$property]);
+	}
+	
+	/**
+	 * Get all requirements. If prefix is provided then only the requirements for 
+	 * the properties that start with prefix will be returned.
+	 * 
+	 * @param string $prefix
+	 */
+	public function getRequirements($prefix = null)
+	{
+		// If no prefix is provided return all requirements
+		if (is_null($prefix)) return $this->_docRequirements;
+		
+		// Find requirements for all properties starting with prefix
+		$properties = array_filter(array_keys($this->_docRequirements), function($value) use ($prefix) {
+			return (substr_compare($value, $prefix, 0, strlen($prefix)) == 0 && strlen($value) > strlen($prefix));
+		});
+		
+		$requirements = array_intersect_key($this->_docRequirements, array_flip($properties));
+		
+		// Remove prefix from requirement key
+		$newRequirements = array();
+		array_walk($requirements, function($value, $key) use ($prefix, &$newRequirements) {
+			$newRequirements[substr($key, strlen($prefix))] = $value;
+		});
+		
+		return $newRequirements;
+	}
+	
+	/**
+	 * Add a requirement to a property
+	 * 
+	 * @param string $property
+	 * @param string $requirement
+	 */
+	public function addRequirement($property, $requirement, $options = null)
+	{
+		if (!array_key_exists($property, $this->_docRequirements)) {
+			$this->_docRequirements[$property] = array();
+		}
+		
+		$this->_docRequirements[$property][$requirement] = $options;
+		unset($this->_filters[$property]);
+		unset($this->_validators[$property]);
+	}
+	
+	/**
+	 * Remove a requirement from a property
+	 * 
+	 * @param string $property
+	 * @param string $requirement
+	 */
+	public function removeRequirement($property, $requirement)
+	{
+		if (!array_key_exists($property, $this->_docRequirements)) return;
+		
+		foreach ($this->_docRequirements[$property] as $requirementItem => $options) {
+			if ($requirement === $requirementItem) {
+				unset($this->_docRequirements[$property][$requirementItem]);
+				unset($this->_filters[$property]);
+				unset($this->_validators[$property]);
+			}
+		}
+	}
+	
+	/**
+	 * Get all the properties with a particular requirement
+	 * 
+	 * @param array $requirement
+	 */
+	public function getPropertiesWithRequirement($requirement)
+	{
+		$properties = array();
+		
+		foreach ($this->_docRequirements as $property => $requirementList) {
+			if (strpos($property, '.') > 0) continue;
+			
+			if (array_key_exists($requirement, $requirementList)) {
+				$properties[] = $property;
+			}
+		}
+		
+		return $properties;
+	}
+
+	/**
+	 * Load the requirements as validators or filters for a given property,
+	 * and cache them as validators or filters, respectively.
+	 *
+	 * @param String $property Name of property
+	 * @return boolean whether or not cache was used. 
+	 */
+	public function loadRequirements($property)
+	{
+		if (isset($this->_validators[$property]) || isset($this->_filters[$property])) {
+			return true;
+		}
+
+		$validators = new Zend_Validate;
+		$filters = new Zend_Filter;
+
+		if (!isset($this->_docRequirements[$property])) {
+			$this->_filters[$property] = $filters;
+			$this->_validators[$property] = $validators;
+			return false;
+		}
+
+		foreach ($this->_docRequirements[$property] as $requirement => $options) {
+			$req = Mooses_Mongodb_Mongo::retrieveRequirement($requirement, $options);
+			if ($req instanceof Zend_Validate_Interface) {
+				$validators->addValidator($req);
+			} else if ($req instanceof Zend_Filter_Interface) {
+				$filters->addFilter($req);
+			}
+		}
+		$this->_filters[$property] = $filters;
+		$this->_validators[$property] = $validators;
+		return false;
+	}
+	
+	/**
+	 * Get all validators attached to a property
+	 * 
+	 * @param String $property Name of property
+	 * @return Zend_Validate
+	 **/
+	public function getValidators($property)
+	{
+		$this->loadRequirements($property);
+		return $this->_validators[$property];
+	}
+	
+	/**
+	 * Get all filters attached to a property
+	 * 
+	 * @param String $property
+	 * @return Zend_Filter
+	 */
+	public function getFilters($property)
+	{
+		$this->loadRequirements($property);
+		return $this->_filters[$property];
+	}
+	
+	
+	/**
+	 * Test if a value is valid against a property
+	 * 
+	 * @param String $property
+	 * @param Boolean $value
+	 */
+	public function isValid($property, $value)
+	{
+		$validators = $this->getValidators($property);
+		
+		return $validators->isValid($value);
+	}
+        
+        public function getAllData(){
+            return array_diff_key($this->_cleanData, array("_type" => NULL));
+        }
+        
+        public function getKeys(){
+            return array_keys($this->_cleanData);
+        }
+	
+	/**
+	 * Get a property
+	 * 
+	 * @param mixed $property
+	 */
+	public function getProperty($property)
+	{
+		// If property exists and initialised then return it
+		if (array_key_exists($property, $this->_data)) {
+			return $this->_data[$property];
+		}
+
+		// Fetch clean data for this property
+		if (array_key_exists($property, $this->_cleanData)) {
+			$data = $this->_cleanData[$property];
+		}
+		else {
+			$data = array();
+		}
+
+		// If data is not an array then we can do nothing else with it
+		if (!is_array($data)) {
+			$this->_data[$property] = $data;
+			return $this->_data[$property];
+		}
+	
+		// If property is supposed to be an array then initialise an array
+		if ($this->hasRequirement($property, 'Array')) {
+			return $this->_data[$property] = $data;
+		}
+		
+		// If property is a reference to another document then fetch the reference document
+		$db = $this->getConfigAttribute('db');
+		if (MongoDBRef::isRef($data)) {
+			$collection = $data['$ref'];
+			$data = MongoDBRef::get($this->_getMongoDB(false), $data);
+			
+			// If this is a broken reference then no point keeping it for later
+			if (!$data) {
+				$this->_data[$property] = null;
+				return $this->_data[$property];
+			}
+			
+			$reference = true;
+		}
+		else {
+			$collection = $this->getConfigAttribute('collection');
+			$reference = false;
+		}
+		
+		// Find out the class name of the document or document set we are loaded
+		if ($className = $this->hasRequirement($property, 'DocumentSet')) {
+			$docType = 'Mooses_Mongodb_Mongo_DocumentSet';
+		}
+		else {
+			$className = $this->hasRequirement($property, 'Document');
+			
+			// Load a document anyway so long as $data is not empty
+			if (!$className && !empty($data)) {
+				$className = 'Mooses_Mongodb_Mongo_Document';
+			}
+			
+			if ($className) $docType = 'Mooses_Mongodb_Mongo_Document';
+		}
+		
+		// Nothing else to do
+		if (!$className) return null;
+		
+		// Configure property for document/documentSet usage
+		$config = array();
+		$config['new'] = empty($data);
+		$config['connectionGroup'] = $this->getConfigAttribute('connectionGroup');
+		$config['db'] = $this->getConfigAttribute('db');
+		$config['collection'] = $collection;
+		$config['requirementModifiers'] = $this->getRequirements($property.'.');
+		$config['hasId'] = $this->hasRequirement($property, 'hasId');
+		
+		if (!$reference) {
+			$config['pathToDocument'] = $this->getPathToProperty($property);
+			$config['criteria'] = $this->getCriteria();
+		}
+		
+		// Initialise document
+		$document = new $className($data, $config);
+		
+		// if this document was a reference then remember that
+		if ($reference) {
+			$this->_references->attach($document);
+		}
+		
+		$this->_data[$property] = $document;
+		return $this->_data[$property];
+	}
+	
+	/**
+	 * Set a property
+	 * 
+	 * @param mixed $property
+	 * @param mixed $value
+	 */
+	public function setProperty($property, $value, $_forceCleanData = false)
+	{
+		
+		$validators = $this->getValidators($property);
+		
+		// Throw exception if value is not valid
+		if (!is_null($value) && !$validators->isValid($value)) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception(implode($validators->getMessages(), "\n"));
+		}
+		
+		// Unset property
+		if (is_null($value)) {
+			$this->_data[$property] = null;
+			return;
+		}
+		
+		if ($value instanceof Mooses_Mongodb_Mongo_Document && !$this->hasRequirement($property, 'AsReference')) {
+			if (!$value->isNewDocument() || !$value->isRootDocument()) {
+				$documentClass = get_class($value);
+				$value = new $documentClass($value->export(), array('new' => false, 'pathToDocument' => $this->getPathToProperty($property)));
+			}
+			else {
+				$value->setPathToDocument($this->getPathToProperty($property));
+			}
+			
+			$value->setConfigAttribute('connectionGroup', $this->getConfigAttribute('connectionGroup'));
+			$value->setConfigAttribute('db', $this->getConfigAttribute('db'));
+			$value->setConfigAttribute('collection', $this->getConfigAttribute('collection'));
+			$value->setConfigAttribute('criteria', $this->getCriteria());
+			$value->applyRequirements($this->getRequirements($property.'.'));
+		}
+		
+		// Filter value
+		$value = $this->getFilters($property)->filter($value);
+		if($_forceCleanData) {
+			$this->_cleanData[$property] = $value;
+		}
+		$this->_data[$property] = $value;
+	}
+	
+	/**
+	 * Determine if this document has a property
+	 * 
+	 * @param $property
+	 * @return boolean
+	 */
+	public function hasProperty($property)
+	{
+		// If property has been initialised
+		if (array_key_exists($property, $this->_data)) {
+			return !is_null($this->_data[$property]);
+		}
+		
+		// If property has not been initialised
+		if (array_key_exists($property, $this->_cleanData)) {
+			return !is_null($this->_cleanData[$property]);
+		}
+		
+		return false;
+	}
+
+	/**
+	 * Get a list of all property keys in this document
+	 */
+	public function getPropertyKeys()
+	{
+		$keyList = array();
+		$doNoCount = array();
+		
+		foreach ($this->_data as $property => $value) {
+			if (($value instanceof Mooses_Mongodb_Mongo_Document && !$value->isEmpty()) ||
+				(!($value instanceof Mooses_Mongodb_Mongo_Document) && !is_null($value))) {
+				$keyList[] = $property;
+			}
+			else {
+				$doNoCount[] = $property;
+			}
+		}
+		
+		foreach ($this->_cleanData as $property => $value) {
+			if (in_array($property, $keyList, true) || in_array($property, $doNoCount, true)) continue;
+			
+			if (!is_null($value)) $keyList[] = $property;
+		}
+		
+		return $keyList;
+	}
+	
+	/**
+	 * Create a reference to this document
+	 * 
+	 * @return array
+	 */
+	public function createReference()
+	{
+		if (!$this->isRootDocument()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not create reference. Document is not a root document');
+		}
+		
+		if (!$this->isConnected()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not create reference. Document does not connected to a db and collection');
+		}
+		
+		return MongoDBRef::create($this->getConfigAttribute('collection'), $this->getId());
+	}
+	
+	/**
+	 * Test to see if a document is a reference in this document
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Document $document
+	 * @return boolean
+	 */
+	public function isReference(Mooses_Mongodb_Mongo_Document $document)
+	{
+		return $this->_references->contains($document);
+	}
+	
+	/**
+    * Determine if the document has a given reference or not
+    *
+    * @Return Boolean
+    */
+    public function hasReference($referenceName)
+    {
+        return !is_null($this->getProperty($referenceName));
+    }
+    
+	/**
+	 * Export all data
+	 * 
+	 * @return array
+	 */
+	public function export($skipRequired = false)
+	{
+		$exportData = $this->_cleanData;
+		
+		foreach ($this->_data as $property => $value) {
+			// If property has been deleted
+			if (is_null($value)) {
+				unset($exportData[$property]);
+				continue;
+			}
+			
+			// If property is a document
+			if ($value instanceof Mooses_Mongodb_Mongo_Document) {
+				// Make when exporting from a documentset look up the correct requirement index
+				if ($this instanceof Mooses_Mongodb_Mongo_DocumentSet) {
+					$requirementIndex = Mooses_Mongodb_Mongo_DocumentSet::DYNAMIC_INDEX;
+				}
+				else {
+					$requirementIndex = $property;
+				}
+				
+				// If document is supposed to be a reference
+				if ($this->hasRequirement($requirementIndex, 'AsReference') || $this->isReference($value)) {
+					$exportData[$property] = $value->createReference();
+					continue;
+				}
+				
+				$data = $value->export();
+				if (!empty($data)) {
+					$exportData[$property] = $data;
+				}
+				continue;
+			}
+			
+			$exportData[$property] = $value;
+		}
+
+		if (!$skipRequired) {
+
+			// make sure required properties are not empty
+			$requiredProperties = $this->getPropertiesWithRequirement('Required');
+			foreach ($requiredProperties as $property) {
+				if (!isset($exportData[$property]) || (is_array($exportData[$property]) && empty($exportData[$property]))) {
+					require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+					throw new Mooses_Mongodb_Mongo_Exception("Property '{$property}' must not be null.");
+				}
+			}
+
+		}
+				
+		return $exportData;
+	}
+	
+	/**
+	 * Is this document a new document
+	 * 
+	 * @return boolean
+	 */
+	public function isNewDocument()
+	{
+		return ($this->_config['new']);
+	}
+	
+	/**
+	 * Test to see if this document is empty
+	 * 
+	 * @return Boolean
+	 */
+	public function isEmpty()
+	{
+		$doNoCount = array();
+		
+		foreach ($this->_data as $property => $value) {
+			if ($value instanceof Mooses_Mongodb_Mongo_Document) {
+				if (!$value->isEmpty()) return false;
+			}
+			elseif (!is_null($value)) {
+				return false;
+			}
+			
+			$doNoCount[] = $property;
+		}
+		
+		foreach ($this->_cleanData as $property => $value) {
+			if (in_array($property, $doNoCount)) {
+				continue;
+			}
+			
+			if (!is_null($value)) {
+				return false;
+			}
+		}
+		
+		return true;
+	}
+	
+	/**
+	 * Convert data changes into operations
+	 * 
+	 * @param array $data
+	 */
+	public function processChanges(array $data = array())
+	{
+		foreach ($data as $property => $value) {
+			if ($property === '_id') continue;
+			
+			if (!array_key_exists($property, $this->_cleanData)) {
+				$this->addOperation('$set', $property, $value);
+				continue;
+			}
+			
+			$newValue = $value;
+			$oldValue = $this->_cleanData[$property];
+			
+			if (MongoDBRef::isRef($newValue) && MongoDBRef::isRef($oldValue)) {
+				$newValue['$id'] = $newValue['$id']->__toString();
+				$oldValue['$id'] = $oldValue['$id']->__toString();
+			}
+			
+			if ($newValue !== $oldValue) {
+				$this->addOperation('$set', $property, $value);
+			}
+		}
+		
+		foreach ($this->_cleanData as $property => $value) {
+			if (array_key_exists($property, $data)) continue;
+			
+			$this->addOperation('$unset', $property, 1);
+		}
+	}
+	
+	/**
+	 * Removes any properties that have been flagged as ignore in properties.
+	 *
+	 * @return void
+	 * @author Tom Holder
+	 **/
+	public function removeIgnoredProperties(&$exportData)
+	{
+		// remove ignored properties
+		$ignoreProperties = $this->getPropertiesWithRequirement('Ignore');
+		
+		foreach ($this->_data as $property => $document) {
+			if (!($document instanceof Mooses_Mongodb_Mongo_Document)) continue;
+			if ($this->isReference($document) || $this->hasRequirement($property, 'AsReference')) continue;
+
+			$document->removeIgnoredProperties($exportData[$property]);
+		}
+			
+		foreach ($ignoreProperties as $property) {
+			unset($exportData[$property]);
+		}
+	}
+	
+	/**
+	 * Save this document
+	 * 
+	 * @param boolean $entierDocument Force the saving of the entier document, instead of just the changes
+	 * @param boolean $safe If FALSE, the program continues executing without waiting for a database response. If TRUE, the program will wait for the database response and throw a MongoCursorException if the update did not succeed
+	 * @return boolean Result of save
+	 */
+	public function save($entierDocument = false, $safe = true)
+	{
+		if (!$this->isConnected()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not save documet. Document is not connected to a db and collection');
+		}
+		
+		if ($this->isLocked()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not save documet. Document is locked.');
+		}
+		
+		## execute pre hooks
+		if ($this->isNewDocument()) $this->preInsert();
+		else $this->preUpdate();
+		
+		$this->preSave();
+		
+		$exportData = $this->export();
+		
+		//Remove data with Ignore requirement.
+		$this->removeIgnoredProperties($exportData);
+		
+		if ($this->isRootDocument() && ($this->isNewDocument() || $entierDocument)) {
+			// Save the entier document
+			$operations = $exportData;
+		}
+		else {
+			// Update an existing document and only send the changes
+			if (!$this->isRootDocument()) {
+				// are we updating a child of an array?
+				if ($this->isNewDocument() && $this->isParentDocumentSet()) {
+					$this->_operations['$push'][$this->getPathToDocument()] = $exportData;
+					$exportData = array();
+					
+					/**
+					 * We need to lock this document because it has an incomplete document path and there is no way to find out it's true path.
+					 * Locking prevents overriding the parent array on another save() after this save().
+					 */
+					$this->setConfigAttribute('locked', true);
+				}
+			}
+			
+			// Convert all data changes into sets and unsets
+			$this->processChanges($exportData);
+			
+			$operations = $this->getOperations(true);
+
+			// There are no changes, return so we don't blank the object
+			if (empty($operations)) {
+				return true;
+			}
+		}
+		
+		$result = false;
+		
+		if($this->isNewDocument())
+		{
+			$result = $this->_getMongoCollection(true)->update($this->getCriteria(), $operations, array('upsert' => true, 'safe' => $safe));
+			$this->_cleanData = $exportData;
+		}
+		else
+		{
+			$newversion = $this->_getMongoDb(true)->command(
+				array(
+						'findandmodify' => $this->getConfigAttribute('collection'), 
+						'query' => $this->getCriteria(), 
+						'update'=>$operations,
+						'new'=>true )
+						);
+
+			if(isset($newversion['value']))
+				$this->_cleanData = $newversion['value'];
+
+			if($newversion['ok'] == 1)
+				$result = true;
+		}
+
+		$this->_data = array();
+		$this->purgeOperations(true);
+		
+		// Run post hooks
+		if ($this->isNewDocument()) {
+			// This is not a new document anymore
+			$this->setConfigAttribute('new', false);
+
+			$this->postInsert();
+		}
+		else {
+			$this->postUpdate();
+		}
+		
+		$this->postSave();
+		
+		return $result;
+	}
+	
+	/**
+	 * Save this document without waiting for a response from the server
+	 * 
+	 * @param boolean $entierDocument Force the saving of the entier document, instead of just the changes
+	 * @return boolean Result of save
+	 */
+	public function saveUnsafe($entierDocument = false)
+	{
+		return $this->save($entierDocument, false);
+	}
+	
+	/**
+	 * Delete this document
+	 * 
+	 * $return boolean Result of delete
+	 */
+	public function delete($safe = true)
+	{
+		if (!$this->isConnected()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not delete document. Document is not connected to a db and collection');
+		}
+	
+		if ($this->isLocked()) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception('Can not save documet. Document is locked.');
+		}
+		
+		$mongoCollection = $this->_getMongoCollection(true);
+		
+		// Execute pre delete hook
+		$this->preDelete();
+		
+		if (!$this->isRootDocument()) {
+			$result = $mongoCollection->update($this->getCriteria(), array('$unset' => array($this->getPathToDocument() => 1)), array('safe' => $safe));
+		}
+		else {
+			$result = $mongoCollection->remove($this->getCriteria(), array('justOne' => true, 'safe' => $safe));
+		}
+		
+		// Execute post delete hook
+		$this->postDelete();
+		
+		return $result;
+	}
+	
+	/**
+	 * Get a property
+	 * 
+	 * @param $property
+	 * @return mixed
+	 */
+	public function __get($property)
+	{
+		return $this->getProperty($property);
+	}
+	
+	/**
+	 * Set a property
+	 * 
+	 * @param string $property
+	 * @param mixed $value
+	 */
+	public function __set($property, $value)
+	{
+		return $this->setProperty($property, $value);
+	}
+	
+	/**
+	 * Test to see if a property is set
+	 * 
+	 * @param string $property
+	 */
+	public function __isset($property)
+	{
+		return $this->hasProperty($property);
+	}
+	
+	/**
+	 * Unset a property
+	 * 
+	 * @param string $property
+	 */
+	public function __unset($property)
+	{
+		$this->_data[$property] = null;
+	}
+	
+	/**
+	 * Get an offset
+	 * 
+	 * @param string $offset
+	 * @return mixed
+	 */
+	public function offsetGet($offset)
+	{
+		return $this->getProperty($offset);
+	}
+	
+	/**
+	 * set an offset
+	 * 
+	 * @param string $offset
+	 * @param mixed $value
+	 */
+	public function offsetSet($offset, $value)
+	{
+		return $this->setProperty($offset, $value);
+	}
+	
+	/**
+	 * Test to see if an offset exists
+	 * 
+	 * @param string $offset
+	 */
+	public function offsetExists($offset)
+	{
+		return $this->hasProperty($offset);
+	}
+	
+	/**
+	 * Unset a property
+	 * 
+	 * @param string $offset
+	 */
+	public function offsetUnset($offset)
+	{
+		$this->_data[$offset] = null;
+	}
+	
+	/**
+	 * Count all properties in this document
+	 * 
+	 * @return int
+	 */
+	public function count()
+	{
+		return count($this->getPropertyKeys());
+	}
+	
+	/**
+	 * Get the document iterator
+	 * 
+	 * @return Mooses_Mongodb_Mongo_DocumentIterator
+	 */
+	public function getIterator()
+	{
+		return new Mooses_Mongodb_Mongo_Iterator_Default($this);
+	}
+	
+	/**
+	 * Get all operations
+	 * 
+	 * @param Boolean $includingChildren Get operations from children as well
+	 */
+	public function getOperations($includingChildren = false)
+	{
+		$operations = $this->_operations;
+		if ($includingChildren) {
+			foreach ($this->_data as $property => $document) {
+				if (!($document instanceof Mooses_Mongodb_Mongo_Document)) continue;
+				
+				if (!$this->isReference($document) && !$this->hasRequirement($property, 'AsReference')) {
+					$operations = array_merge_recursive($operations, $document->getOperations(true));
+				}
+			}
+		}
+		
+		return $operations;
+	}
+	
+	/**
+	 * Remove all operations
+	 * 
+	 * @param Boolean $includingChildren Remove operations from children as wells
+	 */
+	public function purgeOperations($includingChildren = false)
+	{
+		if ($includingChildren) {
+			foreach ($this->_data as $property => $document) {
+				if (!($document instanceof Mooses_Mongodb_Mongo_Document)) continue;
+				
+				if (!$this->isReference($document) || $this->hasRequirement($property, 'AsReference')) {
+					$document->purgeOperations(true);
+				}
+			}
+		}
+		
+		$this->_operations = array();
+	}
+	
+	/**
+	 * Add an operation
+	 * 
+	 * @param string $operation
+	 * @param array $data
+	 */
+	public function addOperation($operation, $property = null, $value = null)
+	{
+		// Make sure the operation is valid
+		if (!Mooses_Mongodb_Mongo::isValidOperation($operation)) {
+			require_once 'Thinkopen/Mongdb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception("'{$operation}' is not valid operation");
+		}
+		
+		// Prime the specific operation
+		if (!array_key_exists($operation, $this->_operations)) {
+			$this->_operations[$operation] = array();
+		}
+		
+		// Save the operation
+		if (is_null($property)) {
+			$path = $this->getPathToDocument();
+		}
+		else {
+			$path = $this->getPathToProperty($property);
+		}
+		
+		// Mix operation with existing operations if needed
+		switch($operation) {
+			case '$pushAll':
+			case '$pullAll':
+				if (!array_key_exists($path, $this->_operations[$operation])) {
+					break;
+				}
+				
+				$value = array_merge($this->_operations[$operation][$path], $value);
+				break;
+		}
+		
+		$this->_operations[$operation][$path] = $value;
+	}
+	
+	/**
+	 * Increment a property by a specified amount
+	 * 
+	 * @param string $property
+	 * @param int $value
+	 */
+	public function inc($property, $value = 1)
+	{
+		return $this->addOperation('$inc', $property, $value);
+	}
+	
+	/**
+	 * Push a value to a property
+	 * 
+	 * @param string $property
+	 * @param mixed $value
+	 */
+	public function push($property = null, $value = null)
+	{
+		// Export value if needed
+		if ($value instanceof Mooses_Mongodb_Mongo_Document) {
+			$value = $value->export();
+		}
+		
+		return $this->addOperation('$pushAll', $property, array($value));
+	}
+	
+	/**
+	 * Pull all occurrences a value from an array
+	 * 
+	 * @param string $property
+	 * @param mixed $value
+	 */
+	public function pull($property, $value)
+	{
+		return $this->addOperation('$pullAll', $property, $value);
+	}
+	
+	/*
+	 * Adds value to the array only if its not in the array already.
+	 * 
+	 * @param string $property
+	 * @param mixed $value
+	 */
+	public function addToSet($property, $value)
+	{
+		return $this->addOperation('$addToSet', $property, $value);
+	}
+	
+	/*
+	 * Removes an element from an array
+	 * 
+	 * @param string $property
+	 * @param mixed $value
+	 */
+	public function pop($property, $value)
+	{
+		return $this->addOperation('$pop', $property, $value);
+	}
+}

+ 259 - 0
lib/Mooses/Mongodb/Mongo/DocumentSet.php

@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @license    New BSD License
+ */
+class Mooses_Mongodb_Mongo_DocumentSet extends Mooses_Mongodb_Mongo_Document
+{
+	const DYNAMIC_INDEX = '$';
+	
+	protected static $_requirements = array(
+		self::DYNAMIC_INDEX => 'Document'
+	);
+	
+	/**
+	 * Get the property keys for this Document Set
+	 * 
+	 * @return array
+	 */
+	public function getPropertyKeys()
+	{
+		$keys = parent::getPropertyKeys();
+		sort($keys, SORT_NUMERIC);
+		
+		return $keys;
+	}
+	
+	/**
+	 * Get a property
+	 * 
+	 * @param mixed $property
+	 */
+	public function getProperty($index = null)
+	{
+		$new = is_null($index);
+		
+		// If property exists and initialised then return it
+		if (!$new && array_key_exists($index, $this->_data)) {
+			return $this->_data[$index];
+		}
+		
+		// Make sure we are not trying to create a document that is supposed to be saved as a reference
+		if ($new && $this->hasRequirement(static::DYNAMIC_INDEX, 'AsReference')) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception("Can not create a new document from documentset where document must be saved as a reference");
+		}
+
+		if (!$new) {
+			// Fetch clean data for this property if it exists
+			if (array_key_exists($index, $this->_cleanData)) $data = $this->_cleanData[$index];
+			else return null;
+		}
+		else $data = array();
+		
+		// If property is a reference to another document then fetch the reference document
+		if (MongoDBRef::isRef($data)) {
+			$collection = $data['$ref'];
+			$data = MongoDBRef::get($this->_getMongoDB(false), $data);
+			
+			// If this is a broken reference then no point keeping it for later
+			if (!$data) {
+				$this->_data[$index] = null;
+				return $this->_data[$index];
+			}
+			
+			$reference = true;
+		}
+		else {
+			$reference = false;
+			$collection = $this->getConfigAttribute('collection');
+		}
+		
+		$config = array ();
+		$config['new'] = $new;
+		$config['requirementModifiers'] = $this->getRequirements(self::DYNAMIC_INDEX.'.');
+		$config['parentIsDocumentSet'] = true;
+		$config['connectionGroup'] = $this->getConfigAttribute('connectionGroup');
+		$config['db'] = $this->getConfigAttribute('db');
+		$config['collection'] = $collection;
+		
+		if (!$reference) {
+			// If this is a new array element. We will $push to the array when saving
+			if ($new) $path = $this->getPathToDocument();
+			else $path = $this->getPathToProperty($index);
+			
+			$config['pathToDocument'] = $path;
+			$config['criteria'] = $this->getCriteria();
+			$config['hasId'] = $this->hasRequirement(self::DYNAMIC_INDEX, 'hasId');
+		}
+		
+		// get the document class
+		$documentClass = $this->hasRequirement(self::DYNAMIC_INDEX, 'Document');
+		if (isset($data['_type']) && !empty($data['_type'][0])) {
+			$documentClass = $data['_type'][0];
+		}
+		$document = new $documentClass($data, $config);
+		
+		// if this document was a reference then remember that
+		if ($reference) {
+			$this->_references->attach($document);
+		}
+		
+		// If this is not a new document cache it
+		if (!$new) {
+			$this->_data[$index] = $document;
+		}
+		
+		return $document;
+	}
+	
+	/**
+	 * Set property
+	 * 
+	 * @param $index
+	 * @param $document
+	 */
+	public function setProperty($index, $document)
+	{
+		$new = is_null($index);
+		
+		// Make sure index is numeric
+		if (!$new && !is_numeric($index)) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception("Index must be numeric '{$index}' given");
+		}
+		
+		// Unset element
+		if (!$new && is_null($document)) {
+			$this->_data[$index] = null;
+			return;
+		}
+		
+		// Make sure we are not keeping a copy of the old document in reference memory
+		if (!$new && isset($this->_data[$index]) && !is_null($this->_data[$index])) {
+			$this->_references->detach($this->_data[$index]);
+		}
+		
+		// Throw exception if value is not valid
+		$validators = $this->getValidators(self::DYNAMIC_INDEX);
+		
+		if (!$validators->isValid($document)) {
+			require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+			throw new Mooses_Mongodb_Mongo_Exception(implode($validators->getMessages(), "\n"));
+		}
+		
+		if ($new) {
+			$keys = $this->getPropertyKeys();
+			$index = empty($keys) ? 0 : max($keys)+1;
+		}
+
+		// Filter value
+//		$value = $this->getFilters(self::DYNAMIC_INDEX)->filter($document);
+
+		if (!$this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) {
+			// Make a new document if it has been saved somewhere else
+			if (!$document->isNewDocument()) {
+				$documentClass = get_class($document);
+				$document = new $documentClass($document->export(), array('new' => false, 'pathToDocument' => $this->getPathToProperty($index)));
+			}
+			else {
+				$document->setPathToDocument($this->getPathToProperty($index));
+			}
+			
+			// Inform the document of it's surroundings
+			$document->setConfigAttribute('connectionGroup', $this->getConfigAttribute('connectionGroup'));
+			$document->setConfigAttribute('db', $this->getConfigAttribute('db'));
+			$document->setConfigAttribute('collection', $this->getConfigAttribute('collection'));
+			$document->setConfigAttribute('criteria', $this->getCriteria());
+			$document->applyRequirements($this->getRequirements(self::DYNAMIC_INDEX.'.'));
+		}
+		
+		$this->_data[$index] = $document;
+	}
+	
+	/**
+	 * Export all data
+	 * 
+	 * @return array
+	 */
+	public function export($skipRequired = false)
+	{
+		// Since this is an array, fill in empty index's with null
+		$exportData = parent::export($skipRequired);
+		
+		// Fix PHP "max(): Array must contain at least one element" bug
+        // if DocumentSet has no data
+        if (count($exportData) > 0) {
+    		$maxKey = max(array_keys($exportData));
+    		
+    		for ($i = 0; $i<$maxKey; $i++) {
+    			if (array_key_exists($i, $exportData)) {
+    				continue;
+    			}
+    			
+    			$exportData[$i] = null;
+    		}
+    		
+    		ksort($exportData);
+        }
+		
+		return $exportData;
+	}
+	
+	/**
+	 * Add a document to this set
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Document $document
+	 */
+	public function addDocument(Mooses_Mongodb_Mongo_Document $document)
+	{
+		return $this->setProperty(null, $document);
+	}
+	
+	/**
+	 * Add a document to the push queue
+	 * 
+	 * @param Mooses_Mongodb_Mongo_Document $document
+	 */
+	public function pushDocument(Mooses_Mongodb_Mongo_Document $document)
+	{
+		$this->push(null, $document);
+	}
+	
+	/**
+	 * Get all operations
+	 * 
+	 * @param Boolean $includingChildren Get operations from children as well
+	 */
+	public function getOperations($includingChildren = false)
+	{
+		if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false;
+		
+		return parent::getOperations($includingChildren);
+	}
+	
+	/**
+	 * Remove all operations
+	 * 
+	 * @param Boolean $includingChildren Remove operations from children as wells
+	 */
+	public function purgeOperations($includingChildren = false)
+	{
+		if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false;
+		
+		return parent::purgeOperations($includingChildren);
+	}
+	
+	public function __call($name, $arguments = array())
+	{
+		switch ($name) {
+			case 'new':
+				return $this->getProperty();
+		}
+		
+		require_once 'Thinkopen/Mongodb/Mongo/Exception.php';
+		throw new Mooses_Mongodb_Mongo_Exception("Captured in __call. Method $name does not exist.");
+	}
+}

+ 11 - 0
lib/Mooses/Mongodb/Mongo/Exception.php

@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @license    New BSD License
+ */
+class Mooses_Mongodb_Mongo_Exception extends Exception
+{
+	
+}

+ 158 - 0
lib/Mooses/Mongodb/Mongo/Iterator/Cursor.php

@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+class Mooses_Mongodb_Mongo_Iterator_Cursor implements OuterIterator
+{
+	protected $_cursor = null;
+	protected $_config = array();
+	
+	public function __construct(MongoCursor $cursor, $config)
+	{
+		$this->_cursor = $cursor;
+		$this->_config = $config;
+	}
+	
+	/**
+	 * Get the inter iterator
+	 * 
+	 * @return MongoCursor
+	 */
+	public function getInnerIterator()
+	{
+		return $this->_cursor;
+	}
+	
+	/**
+	 * Get the document class
+	 * 
+	 * @return string
+	 */
+	public function getDocumentClass()
+	{
+		return $this->_config['documentClass'];
+	}
+	
+	/**
+	 * Get the document set class
+	 * 
+	 * @return string
+	 */
+	public function getDocumentSetClass()
+	{
+		return $this->_config['documentSetClass'];
+	}
+	
+	/**
+	 * Export all data
+	 * 
+	 * @return array
+	 */
+	public function export()
+	{
+		$this->rewind();
+		return iterator_to_array($this->getInnerIterator());
+	}
+	
+	/**
+	 * Construct a document set from this cursor
+	 * 
+	 * @return Mooses_Mongodb_Mongo_DocumentSet
+	 */
+	public function makeDocumentSet()
+	{
+		$config = array();
+		$config['new'] = false;
+		$config['hasId'] = false;
+		$config['connectionGroup'] = $this->_config['connectionGroup'];
+		$config['db'] = $this->_config['db'];
+		$config['collection'] = $this->_config['collection'];
+		$config['requirementModifiers'] = array(
+			Mooses_Mongodb_Mongo_DocumentSet::DYNAMIC_INDEX => array("Document:".$this->getDocumentClass())
+		);
+		
+		$documentSetClass = $this->getDocumentSetClass();
+		return new $documentSetClass($this->export(), $config);
+	}
+	
+	/**
+	 * Get the current value
+	 * 
+	 * @return mixed
+	 */
+	public function current()
+	{
+		$data = $this->getInnerIterator()->current();
+		if ($data === null) {
+		    return null;
+		}
+		
+		$config                    = array();
+		$config['new']             = false;
+		$config['hasKey']          = true;
+		$config['connectionGroup'] = $this->_config['connectionGroup'];
+		$config['db']              = $this->_config['db'];
+		$config['collection']      = $this->_config['collection'];
+		
+		$documentClass = $this->getDocumentClass();
+        if (!empty($data['_type'][0])) {
+            $documentClass = $data['_type'][0];
+        }
+        
+		return new $documentClass($data, $config);
+	}
+	
+	public function getNext()
+	{
+		$this->next();
+		return $this->current();
+	}
+	
+	public function key()
+	{
+		return $this->getInnerIterator()->key();
+	}
+	
+	public function next()
+	{
+		return $this->getInnerIterator()->next();
+	}
+	
+	public function rewind()
+	{
+		return $this->getInnerIterator()->rewind();
+	}
+	
+	public function valid()
+	{
+		return $this->getInnerIterator()->valid();
+	}
+	
+	public function count($all = false)
+	{
+		return $this->getInnerIterator()->count($all);
+	}
+	
+	public function info()
+	{
+		return $this->getInnerIterator()->info();
+	}
+	
+	public function __call($method, $arguments)
+	{
+		// Forward the call to the MongoCursor
+		$res = call_user_func_array(array($this->getInnerIterator(), $method), $arguments);
+		
+		// Allow chaining
+		if ($res instanceof MongoCursor) {
+			return $this;
+		}
+		
+		return $res;
+	}
+}

+ 139 - 0
lib/Mooses/Mongodb/Mongo/Iterator/Default.php

@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Mongodb_Mongo
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Coen Hyde
+ */
+class Mooses_Mongodb_Mongo_Iterator_Default implements SeekableIterator, RecursiveIterator, Countable
+{
+	protected $_document = null;
+	protected $_position = null;
+	protected $_properties = array();
+	protected $_init = false;
+	protected $_counter = 0;
+	
+	public function __construct(Mooses_Mongodb_Mongo_Document $document)
+	{
+		$this->_document = $document;
+		$this->_properties = $document->getPropertyKeys();
+		$this->_position = current($this->_properties);
+		
+		reset($this->_properties);
+	}
+	
+	/**
+	 * Get the document
+	 * 
+	 * @return Mooses_Mongodb_Mongo_Document
+	 */
+	public function getDocument()
+	{
+		return $this->_document;
+	}
+	
+	/**
+	 * Get the properties
+	 * 
+	 * @return array
+	 */
+	public function getDocumentProperties()
+	{
+		return $this->_properties;
+	}
+	
+	/**
+	 * Seek to a position
+	 * 
+	 * @param unknown_type $position
+	 */
+	public function seek($position)
+	{
+		$this->_position = $position;
+		
+		if (!$this->valid()) {
+			throw new OutOfBoundsException("invalid seek position ($position)");
+		}
+	}
+	
+	/**
+	 * Get the current value
+	 * 
+	 * @return mixed
+	 */
+	public function current()
+	{
+		return $this->getDocument()->getProperty($this->key());
+	}
+	
+	/**
+	 * Get the current key
+	 * 
+	 * @return string
+	 */
+	public function key()
+	{
+		return $this->_position;
+	}
+	
+	/**
+	 * Move next
+	 */
+	public function next()
+	{
+		next($this->_properties);
+		$this->_position = current($this->_properties);
+		$this->_counter = $this->_counter + 1;
+	}
+	
+	/**
+	 * Rewind the iterator
+	 */
+	public function rewind()
+	{
+		reset($this->_properties);
+		$this->_position = current($this->_properties);
+	}
+	
+	/**
+	 * Is the current position valid
+	 * 
+	 * @return boolean
+	 */
+	public function valid()
+	{
+		return in_array($this->key(), $this->_properties, true);
+	}
+
+	/**
+	 * Count all properties
+	 * 
+	 * @return int
+	 */
+	public function count()
+	{
+		return count($this->getDocumentProperties());
+	}
+	
+	/**
+	 * Determine if the current node has an children
+	 * 
+	 * @return boolean
+	 */
+	public function hasChildren()
+	{
+		return ($this->current() instanceof Mooses_Mongodb_Mongo_Document);
+	}
+	
+	/*
+	 * Get children
+	 * 
+	 * @return RecursiveIterator
+	 */
+	public function getChildren()
+	{
+		return $this->current()->getIterator();
+	}
+}

+ 22 - 0
lib/Mooses/Mongodb/Mongo/Validate/Array.php

@@ -0,0 +1,22 @@
+<?php
+
+require_once 'Zend/Validate/Abstract.php';
+
+class Mooses_Mongodb_Mongo_Validate_Array extends Zend_Validate_Abstract
+{
+	const NOT_ARRAY = 'notArray';
+	
+	protected $_messageTemplates = array(
+		self::NOT_ARRAY => "Value is not an Array"
+	);
+
+	public function isValid($value)
+	{
+		if (!is_array($value)) {
+			$this->_error(self::NOT_ARRAY);
+			return false;
+		}
+		
+		return true;
+	}
+}

+ 52 - 0
lib/Mooses/Mongodb/Mongo/Validate/Class.php

@@ -0,0 +1,52 @@
+<?php
+
+require_once 'Zend/Validate/Abstract.php';
+
+class Mooses_Mongodb_Mongo_Validate_Class extends Zend_Validate_Abstract
+{
+	const CLASS_NOT_VALID = 'classNotValid';
+	
+	/**
+     * @var array
+     */
+	protected $_messageTemplates = array(
+		self::CLASS_NOT_VALID => "'%value%' is not a %class%"
+	);
+	
+    /**
+     * @var array
+     */
+    protected $_messageVariables = array(
+        'class' => '_class'
+    );
+
+	protected $_class = null;
+
+	public function __construct($class) 
+	{
+		$this->setClass($class);
+	}
+
+	public function setClass($class)
+	{
+		$this->_class = $class;
+	}
+	
+	public function getClass()
+	{
+		return $this->_class;
+	}
+
+	public function isValid($value)
+	{
+		$this->_setValue($value);
+		$class = $this->getClass();
+		
+		if (!($value instanceof $class)) {
+			$this->_error(self::CLASS_NOT_VALID);
+			return false;
+		}
+		
+		return true;
+	}
+}

+ 9 - 0
lib/Mooses/Mongodb/Mongo/Validate/StubTrue.php

@@ -0,0 +1,9 @@
+<?php
+
+class Mooses_Mongodb_Mongo_Validate_StubTrue extends Zend_Validate_Abstract
+{
+	public function isValid($value)
+	{
+		return true;
+	}
+}

+ 56 - 0
lib/Mooses/Mongodb/Paginator/Adapter/Mongo.php

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @see Zend_Paginator_Adapter_Interface
+ */
+require_once 'Zend/Paginator/Adapter/Interface.php';
+
+/**
+ * @category   Thinkopen
+ * @package    Mooses_Paginator
+ * @copyright  Thinkopen s.r.l.
+ * @license    New BSD License
+ * @author     Stefan Heckler
+ */
+class Mooses_Mongodb_Paginator_Adapter_Mongo implements Zend_Paginator_Adapter_Interface
+{
+    /**
+     * Cursor
+     *
+     * @var Mooses_Mongodb_Mongo_Iterator_Cursor
+     */
+    protected $_cursor = null;
+
+    /**
+     * Constructor.
+     *
+     * @param Mooses_Mongodb_Mongo_Iterator_Cursor $cursor
+     */
+    public function __construct(Mooses_Mongodb_Mongo_Iterator_Cursor $cursor)
+    {
+        $this->_cursor = $cursor;
+    }
+
+    /**
+     * Returns an cursor limited to items for a page.
+     *
+     * @param  integer $offset Page offset
+     * @param  integer $itemCountPerPage Number of items per page
+     * @return Mooses_Mongodb_Mongo_Iterator_Cursor
+     */
+	public function getItems($offset, $itemCountPerPage) 
+	{
+		$cursor = $this->_cursor->skip($offset)->limit($itemCountPerPage);
+		return $cursor;
+	}
+
+    /**
+     * Returns the total number of rows in the cursor.
+     *
+     * @return integer
+     */
+    public function count()
+    {
+        return $this->_cursor->count();
+    }
+}

+ 31 - 0
lib/Mooses/Mongodb/Queue/Abstractadapter.php

@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Created by PhpStorm.
+ * User: paolo
+ * Date: 14/12/15
+ * Time: 16:13
+ * @Db: vodafonectc_ordini
+ * @Collection: orderqueue
+ */
+class Mooses_Mongodb_Queue_Abstractadapter extends Mooses_AbstractMongo
+{
+
+    protected $_fields = array("message_id" => NULL, "queue_id" => NULL, "handle" => 0, "body" => "", "md5" => NULL, "timeout" => 0, "created" => 0 , "retrials" => 0, "retrial_messages" => NULL);
+    public function __construct($_data)
+    {
+        if(count($_data) == 0){
+            $_data = $this->_data;
+        }
+        parent::__construct($_data);
+    }
+
+    public function ___setData($_key, $_value = NULL)
+    {
+        if(in_array($_key, $this->_fields)) {
+            return parent::___setData($_key, $_value, true);
+        } else {
+            return $this;
+        }
+    }
+}

+ 390 - 0
lib/Mooses/Mongodb/Queue/Mongoadapter.php

@@ -0,0 +1,390 @@
+<?php
+require_once 'Zend/Queue/Adapter/AdapterAbstract.php';
+
+/**
+ * Class for using a standard PHP array as a queue
+ *
+ * @category   Zend
+ * @package    Zend_Queue
+ * @subpackage Adapter
+ * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Mooses_Mongodb_Queue_Mongoadapter extends Zend_Queue_Adapter_AdapterAbstract {
+    /**
+     * @var array
+     */
+    protected $_data = array();
+    protected $_dataContainer = NULL;
+    protected $_queueCollection = NULL;
+    protected static $instance = NULL;
+
+    /**
+     * Constructor
+     *
+     * @param  array|Zend_Config $options
+     * @param  Zend_Queue|null $queue
+     * @return void
+     */
+    public function __construct($options, Zend_Queue $queue = null)
+    {
+        $this->_dataContainer = new Mooses_Mongodb_Queue_Abstractadapter();
+        $this->_queueCollection = new Mooses_Mongodb_Queue_Queueadapter();
+        if(is_null($options)){
+            $options = array();
+        }
+        parent::__construct($options, $queue);
+    }
+
+    public static function getInstance() {
+        if(is_null(self::$instance)) {
+            $_calledClass = get_called_class();
+            self::$instance = new $_calledClass();
+        }
+        return self::$instance;
+    }
+
+    /********************************************************************
+     * Queue management functions
+     *********************************************************************/
+
+    /**
+     * Does a queue already exist?
+     *
+     * Throws an exception if the adapter cannot determine if a queue exists.
+     * use isSupported('isExists') to determine if an adapter can test for
+     * queue existance.
+     *
+     * @param  string $name
+     * @return boolean
+     */
+    public function isExists($name)
+    {
+        try {
+            $_existantQueue = $this->_queueCollection->loadByAttribute("queue_name", $name);
+            return (($_existantQueue) ? true : false);
+        } catch(Exception $e){
+            var_dump($e->getMessage());
+            die($e->getTraceAsString());
+        }
+    }
+
+    /**
+     * Create a new queue
+     *
+     * Visibility timeout is how long a message is left in the queue "invisible"
+     * to other readers.  If the message is acknowleged (deleted) before the
+     * timeout, then the message is deleted.  However, if the timeout expires
+     * then the message will be made available to other queue readers.
+     *
+     * @param  string  $name    queue name
+     * @param  integer $timeout default visibility timeout
+     * @return boolean
+     */
+    public function create($name, $timeout=null)
+    {
+        if ($this->isExists($name)) {
+            return false;
+        }
+        if ($timeout === null) {
+            $timeout = self::CREATE_TIMEOUT_DEFAULT;
+        }
+        $_newQueue = $this->_queueCollection->setData("queue_id", uniqid("vfq_"))
+            ->setData("queue_name", $name)
+            ->setData("timeout", 86400);
+        $_newQueue->save(true);
+        return true;
+    }
+
+    /**
+     * Delete a queue and all of it's messages
+     *
+     * Returns false if the queue is not found, true if the queue exists
+     *
+     * @param  string  $name queue name
+     * @return boolean
+     */
+    public function delete($name)
+    {
+        try {
+            $_existantQueue = $this->_queueCollection->loadByAttribute("name", $name);
+            if ($_existantQueue) {
+                $_collectionMessages = $this->_dataContainer->getCollection()->addAttributeToFilter("queue_id", $_existantQueue->getData("queue_id"))->getCollectionData();
+            foreach ($_collectionMessages as $_message) {
+                $_message->delete(false);
+            }
+            $this->_dataContainer->load($_existantQueue->getId())->delete(false);
+        }
+            return true;
+        } catch (Exception $e){
+            return false;
+        }
+    }
+
+    /**
+     * Get an array of all available queues
+     *
+     * Not all adapters support getQueues(), use isSupported('getQueues')
+     * to determine if the adapter supports this feature.
+     *
+     * @return array
+     */
+    public function getQueues()
+    {
+        $_collectionQueues = $this->_queueCollection->getCollection()->getCollectionData();
+        return $_collectionQueues;
+    }
+
+    /**
+     * Return the approximate number of messages in the queue
+     *
+     * @param  Zend_Queue $queue
+     * @return integer
+     * @throws Zend_Queue_Exception
+     */
+    public function count(Zend_Queue $queue=null)
+    {
+        if ($queue === null) {
+            $queue = $this->queue;
+        }
+
+        if ($this->isExists($queue->getName())) {
+            /**
+             * @see Zend_Queue_Exception
+             */
+//            require_once 'Zend/Queue/Exception.php';
+            throw new Zend_Queue_Exception('Queue does not exist');
+        }
+        $_messages = $queue->count();
+        return $_messages;
+    }
+
+    /********************************************************************
+     * Messsage management functions
+     *********************************************************************/
+
+    /**
+     * Send a message to the queue
+     *
+     * @param  string     $message Message to send to the active queue
+     * @param  Zend_Queue $queue
+     * @return Zend_Queue_Message
+     * @throws Zend_Queue_Exception
+     */
+    public function send($message, Zend_Queue $queue=null)
+    {
+        if ($queue === null) {
+            $queue = $this->_queue;
+        }
+
+        if (!$this->isExists($queue->getName())) {
+            throw new Zend_Queue_Exception('Queue does not exist:' . $queue->getName());
+        }
+
+        // create the message
+        $data = array(
+            'message_id' => md5(uniqid(rand(), true)),
+            'body'       => $message,
+            'md5'        => md5($message),
+            'handle'     => null,
+            'created'    => time(),
+            'queue_name' => $queue->getName(),
+            'retrials'   => 0
+        );
+
+        // add $data to the "queue"
+        $_queueAdapter = $queue->getAdapter();
+        $_queueAdapter->setData($data)->save();
+
+        $options = array(
+            'queue' => $queue,
+            'data'  => $data,
+        );
+
+        $classname = "Mooses_Mongodb_Queue_Abstractadapter";
+        if (!class_exists($classname)) {
+            require_once 'Zend/Loader.php';
+            Zend_Loader::loadClass($classname);
+        }
+        return new $classname($options);
+    }
+
+    /**
+     * Get messages in the queue
+     *
+     * @param  integer    $maxMessages  Maximum number of messages to return
+     * @param  integer    $timeout      Visibility timeout for these messages
+     * @param  Zend_Queue $queue
+     * @return Zend_Queue_Message_Iterator
+     */
+    public function receive($maxMessages = null, $timeout = 1800, Zend_Queue $queue = null)
+    {
+        if ($maxMessages === null) {
+            $maxMessages = 1;
+        }
+        if ($timeout === null) {
+            $timeout = self::RECEIVE_TIMEOUT_DEFAULT;
+        }
+        if ($queue === null) {
+            $queue = $this->_queue;
+        }
+        $data = array();
+        if ($maxMessages > 0) {
+            $start_time = microtime(true);
+
+            $count = 0;
+            $_collectionModel = new Mooses_Mongodb_Queue_Abstractadapter();
+            $_collectionData = $_collectionModel
+                ->loadByAttribute("queue_name", $queue->getName(), true);
+            foreach(array_reverse($_collectionData) as $key => $msg){
+                if ($count >= $maxMessages) {
+                    break;
+                }
+                $_messageId = $msg->getData("message_id");
+                $_retrials = (int) $msg->getData("retrials");
+                if (($msg->getData('handle') === null) || (!is_null($msg->getData("handle")) && $_retrials < 3)) {
+                    $_randomId = md5(uniqid(rand(), true));
+                    $_microtime = microtime(true);
+                    $_retrials = (int) ($_retrials + 1);
+                    $msg->setData('handle', $_randomId)->setData("timeout", $_microtime);
+                    $msg->update(array("message_id" => $_messageId), array('$set' => array("handle" => $_randomId, "timeout" => $_microtime, "retrials" => $_retrials)));
+                    $data[] = $msg->getData();
+                    $count++;
+//                } elseif(($msg->getData('handle') === null && $_timeout < $start_time)){
+//                    $_logger = Mooses_Logger::getInstance("vfctc_ordersynch_error");
+//                    $_logger->log("SYNCH_ORDERS", Zend_Log::WARN, "Too early to process this message: {\"message_id\": \"" . $_messageId . "\"}");
+                } elseif($_retrials > 2){
+                    $_logger = Mooses_Logger::getInstance("vfctc_ordersynch_error");
+                    $_logger->log("SYNCH_ORDERS", Zend_Log::EMERG, "Too many retrials on message: {\"message_id\": \"" . $_messageId . "\"}");
+                    $msg->update(array("message_id" => $_messageId), array('$set' => array("queue_name" => "error_queue")));
+                }
+
+            }
+        }
+
+        $options = array(
+            'queue'        => $queue,
+            'data'         => $data,
+            'messageClass' => $queue->getMessageClass(),
+        );
+        $classname = $queue->getMessageSetClass();
+        if (!class_exists($classname)) {
+            require_once 'Zend/Loader.php';
+            Zend_Loader::loadClass($classname);
+        }
+        return new $classname($options);
+    }
+
+    public function addRetryErrorMessage($_messageId, $_error, $_oldRetrialMessages = array()){
+        $_collectionModel = new Mooses_Mongodb_Queue_Abstractadapter();
+        $_message = $_collectionModel->loadByAttribute("message_id", $_messageId, false);
+        if($_message){
+            $_retrialMessages = array();
+            if(is_array($_oldRetrialMessages)) {
+                $_retrialMessages = array_merge($_retrialMessages, $_oldRetrialMessages);
+            }
+            if(is_object($_error) || is_array($_error)){
+                $_error = json_encode($_error);
+            }
+            array_push($_retrialMessages, array("date" => date("Y-m-d H:i:s"), "message" => $_error));
+            $_collectionModel->update(array("message_id" => $_messageId), array('$set' => array("retrial_messages" => $_retrialMessages)));
+        };
+    }
+
+    /**
+     * Delete a message from the queue
+     *
+     * Returns true if the message is deleted, false if the deletion is
+     * unsuccessful.
+     *
+     * @param  Zend_Queue_Message $message
+     * @return boolean
+     * @throws Zend_Queue_Exception
+     */
+    public function deleteMessage(Zend_Queue_Message $message)
+    {
+        try {
+            $queue = new Mooses_Mongodb_Queue_Abstractadapter();
+            $_messageData = $message->toArray();
+            return $queue->remove(array("handle" => $_messageData['handle']), array("justOne" => true));
+        } catch (Exception $e){
+            return false;
+        }
+    }
+
+    /********************************************************************
+     * Supporting functions
+     *********************************************************************/
+
+    /**
+     * Return a list of queue capabilities functions
+     *
+     * $array['function name'] = true or false
+     * true is supported, false is not supported.
+     *
+     * @param  string $name
+     * @return array
+     */
+    public function getCapabilities()
+    {
+        return array(
+            'create'        => true,
+            'delete'        => true,
+            'send'          => true,
+            'receive'       => true,
+            'deleteMessage' => true,
+            'getQueues'     => true,
+            'count'         => true,
+            'isExists'      => true,
+        );
+    }
+
+    /********************************************************************
+     * Functions that are not part of the Zend_Queue_Adapter_Abstract
+     *********************************************************************/
+
+    /**
+     * serialize
+     */
+    public function __sleep()
+    {
+        return array('_data');
+    }
+
+    /*
+     * These functions are debug helpers.
+     */
+
+    /**
+     * returns underlying _data array
+     * $queue->getAdapter()->getData();
+     *
+     * @return $this;
+     */
+    public function getData()
+    {
+        //$this->_dataContainer->
+    }
+
+    /**
+     * sets the underlying _data array
+     * $queue->getAdapter()->setData($data);
+     *
+     * @param array $data
+     * @return $this;
+     */
+    public function setData($data)
+    {
+        if(is_array($data)) {
+            foreach ($data as $_key => $_singleData) {
+                $this->_dataContainer->setData($_key, $_singleData);
+            }
+        }
+        return $this;
+    }
+
+    public function save(){
+        return $this->_dataContainer->save(true, false);
+    }
+
+}

+ 31 - 0
lib/Mooses/Mongodb/Queue/Queueadapter.php

@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Created by PhpStorm.
+ * User: paolo
+ * Date: 14/12/15
+ * Time: 16:13
+ * @Db: vodafonectc_ordini
+ * @Collection: queues
+ */
+class Mooses_Mongodb_Queue_Queueadapter extends Mooses_AbstractMongo
+{
+
+    protected $_fields = array("queue_id" => NULL, "queue_name" => NULL, "timeout" => 0);
+    public function __construct($_data)
+    {
+        if(count($_data) == 0){
+            $_data = $this->_data;
+        }
+        parent::__construct($_data);
+    }
+
+    public function ___setData($_key, $_value = NULL, $_forceCleanData = false)
+    {
+        if(in_array($_key, $this->_fields)) {
+            return parent::___setData($_key, $_value, $_forceCleanData);
+        } else {
+            return $this;
+        }
+    }
+}