- Improved namespace support
[activemongo.git] / lib / ActiveMongo.php
blob7c5b7fc69aa91f1db88e3b30164f97b07dc43906
1 <?php
2 /*
3 +---------------------------------------------------------------------------------+
4 | Copyright (c) 2010 ActiveMongo |
5 +---------------------------------------------------------------------------------+
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions are met: |
8 | 1. Redistributions of source code must retain the above copyright |
9 | notice, this list of conditions and the following disclaimer. |
10 | |
11 | 2. Redistributions in binary form must reproduce the above copyright |
12 | notice, this list of conditions and the following disclaimer in the |
13 | documentation and/or other materials provided with the distribution. |
14 | |
15 | 3. All advertising materials mentioning features or use of this software |
16 | must display the following acknowledgement: |
17 | This product includes software developed by César D. Rodas. |
18 | |
19 | 4. Neither the name of the César D. Rodas nor the |
20 | names of its contributors may be used to endorse or promote products |
21 | derived from this software without specific prior written permission. |
22 | |
23 | THIS SOFTWARE IS PROVIDED BY CÉSAR D. RODAS ''AS IS'' AND ANY |
24 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
26 | DISCLAIMED. IN NO EVENT SHALL CÉSAR D. RODAS BE LIABLE FOR ANY |
27 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
28 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
30 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE |
33 +---------------------------------------------------------------------------------+
34 | Authors: César Rodas <crodas@php.net> |
35 +---------------------------------------------------------------------------------+
38 // array get_document_vars(stdobj $obj) {{{
39 /**
40 * Simple hack to avoid get private and protected variables
42 * @param object $obj
43 * @param bool $include_id
45 * @return array
47 function get_document_vars($obj, $include_id=TRUE)
49 $document = get_object_vars($obj);
50 if ($include_id && $obj->getID()) {
51 $document['_id'] = $obj->getID();
53 return $document;
55 // }}}
57 if (version_compare(PHP_VERSION, '5.3') < 0) {
58 require dirname(__FILE__)."/Objects_compat.php";
59 } else {
60 require dirname(__FILE__)."/Objects.php";
63 /**
64 * ActiveMongo
66 * Simple ActiveRecord pattern built on top of MongoDB. This class
67 * aims to provide easy iteration, data validation before update,
68 * and efficient update.
70 * @author César D. Rodas <crodas@php.net>
71 * @license BSD License
72 * @package ActiveMongo
73 * @version 1.0
76 abstract class ActiveMongo implements Iterator, Countable, ArrayAccess
79 //{{{ Constants
80 const FIND_AND_MODIFY = 0x001;
81 // }}}
83 // properties {{{
84 /**
85 * Current databases objects
87 * @type array
89 private static $_dbs;
90 /**
91 * Current namespace
93 * @type string
95 private static $_namespace = NULL;
96 /**
97 * Specific namespaces for each class
99 * @type string
101 private static $_namespaces = array();
103 * Current collections objects
105 * @type array
107 private static $_collections;
109 * Current connection to MongoDB
111 * @type MongoConnection
113 private static $_conn;
115 * Database name
117 * @type string
119 private static $_db;
121 * List of events handlers
123 * @type array
125 static private $_events = array();
127 * List of global events handlers
129 * @type array
131 static private $_super_events = array();
133 * Host name
135 * @type string
137 private static $_host;
139 * User (Auth)
141 * @type string
143 private static $_user;
146 * Password (Auth)
148 * @type string
150 private static $_pwd;
153 * Current document
155 * @type array
157 private $_current = array();
159 * Result cursor
161 * @type MongoCursor
163 private $_cursor = NULL;
165 * Extended result cursor, used for FindAndModify now
167 * @type int
169 private $_cursor_ex = NULL;
170 private $_cursor_ex_value;
172 * Count the findandmodify result counts
174 * @tyep array
176 private $_findandmodify_cnt = 0;
177 /* value to modify */
178 private $_findandmodify;
180 /* {{{ Silly but useful query abstraction */
181 private $_cached = FALSE;
182 private $_query = NULL;
183 private $_sort = NULL;
184 private $_limit = 0;
185 private $_skip = 0;
186 private $_properties = NULL;
187 /* }}} */
190 * Current document ID
192 * @type MongoID
194 private $_id;
197 * Tell if the current object
198 * is cloned or not.
200 * @type bool
202 private $_cloned = FALSE;
203 // }}}
205 // GET CONNECTION CONFIG {{{
207 // setNameSpace($namespace='') {{{
209 * Set a namespace for all connections is it is called
210 * statically from ActiveMongo or for specific classes
211 * if it is called from an instance.
213 * @param string $namespace
215 * @return bool
217 final static function setNamespace($namespace='')
219 if (preg_match("#^[\-\_a-z0-9]*$#i", $namespace)) {
220 /* sort of standard late binding */
221 if (isset($this)) {
222 $context = get_class($this);
223 } else {
224 $context = get_called_class();
227 if ($context == __CLASS__) {
228 self::$_namespace = $namespace;
229 } else {
230 self::$_namespaces[$context] = $namespace;
232 return TRUE;
234 return FALSE;
236 // }}}
238 // collectionName() {{{
240 * Get CollectionName
242 * Return the collection name (along with its namespace) for
243 * the current object.
245 * Warning: This must not be called statically from outside the
246 * fundtion.
248 * @return string
250 final public function collectionName()
252 $parent = __CLASS__;
253 /* Need to check if $this is instance of $parent
254 * because PHP5.2 fails detecting $this when a non-static
255 * method is called statically from another class ($this is
256 * inherited)
258 if (isset($this) && $this InstanceOf $parent) {
259 $collection = $this->getCollectionName();
260 $context = get_class($this);
261 } else {
262 /* ugly, it might fail if getCollectionName has some refernce to $this */
263 $context = get_called_class();
264 $collection = call_user_func(array($context, 'getCollectionName'));
267 if (isset(self::$_namespaces[$context]) && self::$_namespaces[$context]) {
268 $collection = self::$_namespaces[$context].".{$collection}";
269 } else if (self::$_namespace) {
270 $collection = self::$_namespace.".{$collection}";
273 return $collection;
275 // }}}
277 // string getCollectionName() {{{
279 * Get Collection Name, by default the class name,
280 * but you it can be override at the class itself to give
281 * a custom name.
283 * @return string Collection Name
285 protected function getCollectionName()
287 if (isset($this)) {
288 return strtolower(get_class($this));
289 } else {
290 return strtolower(get_called_class());
293 // }}}
295 // string getDatabaseName() {{{
297 * Get Database Name, by default it is used
298 * the db name set by ActiveMong::connect()
300 * @return string DB Name
302 protected function getDatabaseName()
304 if (is_NULL(self::$_db)) {
305 throw new ActiveMongo_Exception("There is no information about the default DB name");
307 return self::$_db;
309 // }}}
311 // void install() {{{
313 * Install.
315 * This static method iterate over the classes lists,
316 * and execute the setup() method on every ActiveMongo
317 * subclass. You should do this just once.
320 final public static function install()
322 $classes = array_reverse(get_declared_classes());
323 foreach ($classes as $class)
325 if ($class == __CLASS__) {
326 break;
328 if (is_subclass_of($class, __CLASS__)) {
329 $obj = new $class;
330 $obj->setup();
334 // }}}
336 // void connection($db, $host) {{{
338 * Connect
340 * This method setup parameters to connect to a MongoDB
341 * database. The connection is done when it is needed.
343 * @param string $db Database name
344 * @param string $host Host to connect
345 * @param string $user User (Auth)
346 * @param string $pwd Password (Auth)
348 * @return void
350 final public static function connect($db, $host='localhost', $user = NULL, $pwd=NULL)
352 self::$_host = $host;
353 self::$_db = $db;
354 self::$_user = $user;
355 self::$_pwd = $pwd;
357 // }}}
359 // MongoConnection _getConnection() {{{
361 * Get Connection
363 * Get a valid database connection
365 * @return MongoConnection
367 final protected function _getConnection()
369 if (is_NULL(self::$_conn)) {
370 if (is_NULL(self::$_host)) {
371 self::$_host = 'localhost';
373 self::$_conn = new Mongo(self::$_host);
375 if (isset($this)) {
376 $dbname = $this->getDatabaseName();
377 } else {
378 $dbname = self::getDatabaseName();
380 if (!isSet(self::$_dbs[$dbname])) {
381 self::$_dbs[$dbname] = self::$_conn->selectDB($dbname);
383 if ( !is_NULL(self::$_user ) && !is_NULL(self::$_pwd ) ) {
384 self::$_dbs[$dbname]->authenticate(self::$_user,self::$_pwd);
388 return self::$_dbs[$dbname];
390 // }}}
392 // MongoCollection _getCollection() {{{
394 * Get Collection
396 * Get a collection connection.
398 * @return MongoCollection
400 final protected function _getCollection()
402 if (isset($this)) {
403 $colName = $this->CollectionName();
404 } else {
405 $colName = self::CollectionName();
407 if (!isset(self::$_collections[$colName])) {
408 self::$_collections[$colName] = self::_getConnection()->selectCollection($colName);
410 return self::$_collections[$colName];
412 // }}}
414 // }}}
416 // GET DOCUMENT TO SAVE OR UPDATE {{{
418 // getDocumentVars() {{{
420 * getDocumentVars
425 final protected function getDocumentVars()
427 $variables = array();
428 foreach ((array)$this->__sleep() as $var) {
429 if (!property_exists($this, $var)) {
430 continue;
432 $variables[$var] = $this->$var;
434 return $variables;
436 // }}}
438 // bool getCurrentSubDocument(array &$document, string $parent_key, array $values, array $past_values) {{{
440 * Generate Sub-document
442 * This method build the difference between the current sub-document,
443 * and the origin one. If there is no difference, it would do nothing,
444 * otherwise it would build a document containing the differences.
446 * @param array &$document Document target
447 * @param string $parent_key Parent key name
448 * @param array $values Current values
449 * @param array $past_values Original values
451 * @return FALSE
453 final function getCurrentSubDocument(&$document, $parent_key, Array $values, Array $past_values)
456 * The current property is a embedded-document,
457 * now we're looking for differences with the
458 * previous value (because we're on an update).
460 * It behaves exactly as getCurrentDocument,
461 * but this is simples (it doesn't support
462 * yet filters)
464 foreach ($values as $key => $value) {
465 $super_key = "{$parent_key}.{$key}";
466 if (is_array($value)) {
468 * Inner document detected
470 if (!array_key_exists($key, $past_values) || !is_array($past_values[$key])) {
472 * We're lucky, it is a new sub-document,
473 * we simple add it
475 $document['$set'][$super_key] = $value;
476 } else {
478 * This is a document like this, we need
479 * to find out the differences to avoid
480 * network overhead.
482 if (!$this->getCurrentSubDocument($document, $super_key, $value, $past_values[$key])) {
483 return FALSE;
486 continue;
487 } else if (!array_key_exists($key, $past_values) || $past_values[$key] !== $value) {
488 $document['$set'][$super_key] = $value;
492 foreach (array_diff(array_keys($past_values), array_keys($values)) as $key) {
493 $super_key = "{$parent_key}.{$key}";
494 $document['$unset'][$super_key] = 1;
497 return TRUE;
499 // }}}
501 // array getCurrentDocument(bool $update) {{{
503 * Get Current Document
505 * Based on this object properties a new document (Array)
506 * is returned. If we're modifying an document, just the modified
507 * properties are included in this document, which uses $set,
508 * $unset, $pushAll and $pullAll.
511 * @param bool $update
513 * @return array
515 final protected function getCurrentDocument($update=FALSE, $current=FALSE)
517 $document = array();
518 $object = $this->getDocumentVars();
520 if (!$current) {
521 $current = (array)$this->_current;
525 $this->findReferences($object);
527 $this->triggerEvent('before_validate', array(&$object, $current));
528 $this->triggerEvent('before_validate_'.($update?'update':'creation'), array(&$object, $current));
530 foreach ($object as $key => $value) {
531 if ($update) {
532 if (is_array($value) && isset($current[$key])) {
534 * If the Field to update is an array, it has a different
535 * behaviour other than $set and $unset. Fist, we need
536 * need to check if it is an array or document, because
537 * they can't be mixed.
540 if (!is_array($current[$key])) {
542 * We're lucky, the field wasn't
543 * an array previously.
545 $this->runFilter($key, $value, $current[$key]);
546 $document['$set'][$key] = $value;
547 continue;
550 if (!$this->getCurrentSubDocument($document, $key, $value, $current[$key])) {
551 throw new Exception("{$key}: Array and documents are not compatible");
553 } else if(!array_key_exists($key, $current) || $value !== $current[$key]) {
555 * It is 'linear' field that has changed, or
556 * has been modified.
558 $past_value = isset($current[$key]) ? $current[$key] : NULL;
559 $this->runFilter($key, $value, $past_value);
560 $document['$set'][$key] = $value;
562 } else {
564 * It is a document insertation, so we
565 * create the document.
567 $this->runFilter($key, $value, NULL);
568 $document[$key] = $value;
572 /* Updated behaves in a diff. way */
573 if ($update) {
574 foreach (array_diff(array_keys($this->_current), array_keys($object)) as $property) {
575 if ($property == '_id') {
576 continue;
578 $document['$unset'][$property] = 1;
582 if (count($document) == 0) {
583 return array();
586 $this->triggerEvent('after_validate', array(&$document));
587 $this->triggerEvent('after_validate_'.($update?'update':'creation'), array(&$object));
589 return $document;
591 // }}}
593 // }}}
595 // EVENT HANDLERS {{{
597 // addEvent($action, $callback) {{{
599 * addEvent
602 final static function addEvent($action, $callback)
604 if (!is_callable($callback)) {
605 throw new ActiveMongo_Exception("Invalid callback");
608 $class = get_called_class();
609 if ($class == __CLASS__) {
610 $events = & self::$_super_events;
611 } else {
612 $events = & self::$_events[$class];
614 if (!isset($events[$action])) {
615 $events[$action] = array();
617 $events[$action][] = $callback;
618 return TRUE;
620 // }}}
622 // triggerEvent(string $event, Array $events_params) {{{
623 final function triggerEvent($event, Array $events_params = array(), $context=NULL)
625 if (!$context){
626 if (!isset($this)) {
627 $class = get_called_class();
628 $obj = $class;
629 } else {
630 $class = get_class($this);
631 $obj = $this;
633 } else {
634 $class = $context;
635 $obj = $context;
637 $events = & self::$_events[$class][$event];
638 $sevents = & self::$_super_events[$event];
640 /* Super-Events handler receives the ActiveMongo class name as first param */
641 $sevents_params = array_merge(array($class), $events_params);
643 foreach (array('events', 'sevents') as $event_type) {
644 if (count($$event_type) > 0) {
645 $params = "{$event_type}_params";
646 foreach ($$event_type as $fnc) {
647 if (call_user_func_array($fnc, $$params) === FALSE) {
648 return;
654 switch ($event) {
655 case 'before_create':
656 case 'before_update':
657 case 'before_validate':
658 case 'before_delete':
659 case 'before_drop':
660 case 'before_query':
661 case 'after_create':
662 case 'after_update':
663 case 'after_validate':
664 case 'after_delete':
665 case 'after_drop':
666 case 'after_query':
667 $fnc = array($obj, $event);
668 $params = "events_params";
669 if (is_callable($fnc)) {
670 call_user_func_array($fnc, $$params);
672 break;
675 // }}}
677 // void runFilter(string $key, mixed &$value, mixed $past_value) {{{
679 * *Internal Method*
681 * This method check if the current document property has
682 * a filter method, if so, call it.
684 * If the filter returns FALSE, throw an Exception.
686 * @return void
688 protected function runFilter($key, &$value, $past_value)
690 $filter = array($this, "{$key}_filter");
691 if (is_callable($filter)) {
692 $filter = call_user_func_array($filter, array(&$value, $past_value));
693 if ($filter===FALSE) {
694 throw new ActiveMongo_FilterException("{$key} filter failed");
696 $this->$key = $value;
699 // }}}
701 // }}}
703 // void setCursor(MongoCursor $obj) {{{
705 * Set Cursor
707 * This method receive a MongoCursor and make
708 * it iterable.
710 * @param MongoCursor $obj
712 * @return void
714 final protected function setCursor(MongoCursor $obj)
716 $this->_cursor = $obj;
717 $obj->reset();
718 $this->setResult($obj->getNext());
720 // }}}
722 // void setResult(Array $obj) {{{
724 * Set Result
726 * This method takes an document and copy it
727 * as properties in this object.
729 * @param Array $obj
731 * @return void
733 final protected function setResult($obj)
735 /* Unsetting previous results, if any */
736 foreach (array_keys(get_document_vars($this, FALSE)) as $key) {
737 unset($this->$key);
739 $this->_id = NULL;
741 /* Add our current resultset as our object's property */
742 foreach ((array)$obj as $key => $value) {
743 if ($key[0] == '$') {
744 continue;
746 $this->$key = $value;
749 /* Save our record */
750 $this->_current = $obj;
752 // }}}
754 // this find([$_id]) {{{
756 * Simple find.
758 * Really simple find, which uses this object properties
759 * for fast filtering
761 * @return object this
763 final function find($_id = NULL)
765 $vars = get_document_vars($this);
766 $parent_class = __CLASS__;
767 foreach ($vars as $key => $value) {
768 if (!$value) {
769 unset($vars[$key]);
771 if ($value InstanceOf $parent_class) {
772 $this->getColumnDeference($vars, $key, $value);
773 unset($vars[$key]); /* delete old value */
776 if ($_id != NULL) {
777 if (is_array($_id)) {
778 $vars['_id'] = array('$in' => $_id);
779 } else {
780 $vars['_id'] = $_id;
783 $res = $this->_getCollection()->find($vars);
784 $this->setCursor($res);
785 return $this;
787 // }}}
789 // void save(bool $async) {{{
791 * Save
793 * This method save the current document in MongoDB. If
794 * we're modifying a document, a update is performed, otherwise
795 * the document is inserted.
797 * On updates, special operations such as $set, $pushAll, $pullAll
798 * and $unset in order to perform efficient updates
800 * @param bool $async
802 * @return void
804 final function save($async=TRUE)
806 $update = isset($this->_id) && $this->_id InstanceOf MongoID;
807 $conn = $this->_getCollection();
808 $document = $this->getCurrentDocument($update);
809 $object = $this->getDocumentVars();
811 if (isset($this->_id)) {
812 $object['_id'] = $this->_id;
815 if (count($document) == 0) {
816 return; /*nothing to do */
819 /* PRE-save hook */
820 $this->triggerEvent('before_'.($update ? 'update' : 'create'), array(&$document, $object));
822 if ($update) {
823 $conn->update(array('_id' => $this->_id), $document, array('safe' => $async));
824 if (isset($document['$set'])) {
825 foreach ($document['$set'] as $key => $value) {
826 if (strpos($key, ".") === FALSE) {
827 $this->_current[$key] = $value;
828 $this->$key = $value;
829 } else {
830 $keys = explode(".", $key);
831 $key = $keys[0];
832 $arr = & $this->$key;
833 $arrc = & $this->_current[$key];
834 for ($i=1; $i < count($keys)-1; $i++) {
835 $arr = &$arr[$keys[$i]];
836 $arrc = &$arrc[$keys[$i]];
838 $arr [ $keys[$i] ] = $value;
839 $arrc[ $keys[$i] ] = $value;
843 if (isset($document['$unset'])) {
844 foreach ($document['$unset'] as $key => $value) {
845 if (strpos($key, ".") === FALSE) {
846 unset($this->_current[$key]);
847 unset($this->$key);
848 } else {
849 $keys = explode(".", $key);
850 $key = $keys[0];
851 $arr = & $this->$key;
852 $arrc = & $this->_current[$key];
853 for ($i=1; $i < count($keys)-1; $i++) {
854 $arr = &$arr[$keys[$i]];
855 $arrc = &$arrc[$keys[$i]];
857 unset($arr [ $keys[$i] ]);
858 unset($arrc[ $keys[$i] ]);
862 } else {
863 $conn->insert($document, $async);
864 $this->setResult($document);
867 $this->triggerEvent('after_'.($update ? 'update' : 'create'), array($document, $object));
869 return TRUE;
871 // }}}
873 // bool delete() {{{
875 * Delete the current document
877 * @return bool
879 final function delete()
882 $document = array('_id' => $this->_id);
883 if ($this->_cursor InstanceOf MongoCursor) {
884 $this->triggerEvent('before_delete', array($document));
885 $result = $this->_getCollection()->remove($document);
886 $this->triggerEvent('after_delete', array($document));
887 $this->setResult(array());
888 return $result;
889 } else {
890 $criteria = (array) $this->_query;
892 /* remove */
893 $this->triggerEvent('before_delete', array($document));
894 $this->_getCollection()->remove($criteria);
895 $this->triggerEvent('after_delete', array($document));
897 /* reset object */
898 $this->reset();
900 return TRUE;
902 return FALSE;
904 // }}}
906 // Update {{{
908 * Multiple updates.
910 * This method perform multiple updates when a given
911 * criteria matchs (using where).
913 * By default the update is perform safely, but it can be
914 * changed.
916 * After the operation is done, the criteria is deleted.
918 * @param array $value Values to set
919 * @param bool $safe Whether or not peform the operation safely
921 * @return bool
924 function update(Array $value, $safe=TRUE)
926 $this->_assertNotInQuery();
928 $criteria = (array) $this->_query;
929 $options = array('multiple' => TRUE, 'safe' => $safe);
931 /* update */
932 $col = $this->_getCollection();
933 $col->update($criteria, array('$set' => $value), $options);
935 /* reset object */
936 $this->reset();
938 return TRUE;
940 // }}}
942 // void drop() {{{
944 * Delete the current colleciton and all its documents
946 * @return void
948 final static function drop()
950 $class = get_called_class();
951 if ($class == __CLASS__) {
952 return FALSE;
954 $obj = new $class;
955 $obj->triggerEvent('before_drop');
956 $result = $obj->_getCollection()->drop();
957 $obj->triggerEvent('after_drop');
958 if ($result['ok'] != 1) {
959 throw new ActiveMongo_Exception($result['errmsg']);
961 return TRUE;
964 // }}}
966 // int count() {{{
968 * Return the number of documents in the actual request. If
969 * we're not in a request, it will return 0.
971 * @return int
973 final function count()
975 if ($this->valid()) {
976 return $this->_cursor->count();
978 return 0;
980 // }}}
982 // void setup() {{{
984 * This method should contain all the indexes, and shard keys
985 * needed by the current collection. This try to make
986 * installation on development environments easier.
988 function setup()
991 // }}}
993 // batchInsert {{{
994 /**
995 * Perform a batchInsert of objects.
997 * @param array $documents Arrays of documents to insert
998 * @param bool $safe True if a safe will be performed, this means data validation, and wait for MongoDB OK reply
999 * @param bool $on_error_continue If an error happen while validating an object, if it should continue or not
1001 * @return bool
1003 final public static function batchInsert(Array $documents, $safe=TRUE, $on_error_continue=TRUE)
1005 $context = get_called_class();
1007 if (__CLASS__ == $context) {
1008 throw new ActiveMongo_Exception("Invalid batchInsert usage");
1012 if ($safe) {
1013 foreach ($documents as $id => $doc) {
1014 $valid = FALSE;
1015 if (is_array($doc)) {
1016 try {
1017 self::triggerEvent('before_create', array(&$doc), $context);
1018 self::triggerEvent('before_validate', array(&$doc, $doc), $context);
1019 self::triggerEvent('before_validate_creation', array(&$doc, $doc), $context);
1020 $documents[$id] = $doc;
1021 $valid = TRUE;
1022 } catch (Exception $e) {}
1024 if (!$valid) {
1025 if (!$on_error_continue) {
1026 throw new ActiveMongo_FilterException("Document $id is invalid");
1028 unset($documents[$id]);
1033 return self::_getCollection()->batchInsert($documents, array("safe" => $safe));
1035 // }}}
1037 // bool addIndex(array $columns, array $options) {{{
1039 * addIndex
1041 * Create an Index in the current collection.
1043 * @param array $columns L ist of columns
1044 * @param array $options Options
1046 * @return bool
1048 final function addIndex($columns, $options=array())
1050 $default_options = array(
1051 'background' => 1,
1054 if (!is_array($columns)) {
1055 $columns = array($columns => 1);
1058 foreach ($columns as $id => $name) {
1059 if (is_numeric($id)) {
1060 unset($columns[$id]);
1061 $columns[$name] = 1;
1065 foreach ($default_options as $option => $value) {
1066 if (!isset($options[$option])) {
1067 $options[$option] = $value;
1071 $collection = $this->_getCollection();
1073 return $collection->ensureIndex($columns, $options);
1075 // }}}
1077 // Array getIndexes() {{{
1079 * Return an array with all indexes
1081 * @return array
1083 final static function getIndexes()
1085 return self::_getCollection()->getIndexInfo();
1087 // }}}
1089 // string __toString() {{{
1091 * To String
1093 * If this object is treated as a string,
1094 * it would return its ID.
1096 * @return string
1098 function __toString()
1100 return (string)$this->getID();
1102 // }}}
1104 // array sendCmd(array $cmd) {{{
1106 * This method sends a command to the current
1107 * database.
1109 * @param array $cmd Current command
1111 * @return array
1113 final protected function sendCmd($cmd)
1115 return $this->_getConnection()->command($cmd);
1117 // }}}
1119 // ITERATOR {{{
1121 // array getArray() {{{
1123 * Return the current document as an array
1124 * instead of a ActiveMongo object
1126 * @return Array
1128 final function getArray()
1130 return get_document_vars($this);
1132 // }}}
1134 // void reset() {{{
1136 * Reset our Object, delete the current cursor if any, and reset
1137 * unsets the values.
1139 * @return void
1141 final function reset()
1143 if ($this->_cloned) {
1144 throw new ActiveMongo_Exception("Cloned objects can't be reseted");
1146 $this->_properties = NULL;
1147 $this->_cursor = NULL;
1148 $this->_cursor_ex = NULL;
1149 $this->_query = NULL;
1150 $this->_sort = NULL;
1151 $this->_limit = 0;
1152 $this->_skip = 0;
1153 $this->setResult(array());
1155 // }}}
1157 // bool valid() {{{
1159 * Valid
1161 * Return if we're on an iteration and if it is still valid
1163 * @return TRUE
1165 final function valid()
1167 $valid = FALSE;
1168 if (!$this->_cursor_ex) {
1169 if (!$this->_cursor InstanceOf MongoCursor) {
1170 $this->doQuery();
1172 $valid = $this->_cursor InstanceOf MongoCursor && $this->_cursor->valid();
1173 } else {
1174 switch ($this->_cursor_ex) {
1175 case self::FIND_AND_MODIFY:
1176 if ($this->_limit > $this->_findandmodify_cnt) {
1177 $this->_execFindAndModify();
1178 $valid = $this->_cursor_ex_value['ok'] == 1;
1180 break;
1181 default:
1182 throw new ActiveMongo_Exception("Invalid _cursor_ex value");
1186 return $valid;
1188 // }}}
1190 // bool next() {{{
1192 * Move to the next document
1194 * @return bool
1196 final function next()
1198 if ($this->_cloned) {
1199 throw new ActiveMongo_Exception("Cloned objects can't iterate");
1201 if (!$this->_cursor_ex) {
1202 $result = $this->_cursor->next();
1203 $this->current();
1204 return $result;
1205 } else {
1206 switch ($this->_cursor_ex) {
1207 case self::FIND_AND_MODIFY:
1208 $this->_cursor_ex_value = NULL;
1209 break;
1210 default:
1211 throw new ActiveMongo_Exception("Invalid _cursor_ex value");
1215 // }}}
1217 // this current() {{{
1219 * Return the current object, and load the current document
1220 * as this object property
1222 * @return object
1224 final function current()
1226 if (!$this->_cursor_ex) {
1227 $this->setResult($this->_cursor->current());
1228 } else {
1229 switch ($this->_cursor_ex) {
1230 case self::FIND_AND_MODIFY:
1231 if (count($this->_cursor_ex_value) == 0) {
1232 $this->_execFindAndModify();
1234 $this->setResult($this->_cursor_ex_value['value']);
1235 break;
1236 default:
1237 throw new ActiveMongo_Exception("Invalid _cursor_ex value");
1240 return $this;
1242 // }}}
1244 // bool rewind() {{{
1246 * Go to the first document
1248 final function rewind()
1250 if ($this->_cloned) {
1251 throw new ActiveMongo_Exception("Cloned objects can't iterate");
1253 if (!$this->_cursor_ex) {
1254 /* rely on MongoDB cursor */
1255 if (!$this->_cursor InstanceOf MongoCursor) {
1256 $this->doQuery();
1258 $result = $this->_cursor->rewind();
1259 $this->current();
1260 return $result;
1261 } else {
1262 switch ($this->_cursor_ex) {
1263 case self::FIND_AND_MODIFY:
1264 $this->_findandmodify_cnt = 0;
1265 break;
1266 default:
1267 throw new ActiveMongo_Exception("Invalid _cursor_ex value");
1271 // }}}
1273 // }}}
1275 // ARRAY ACCESS {{{
1276 final function offsetExists($offset)
1278 return isset($this->$offset);
1281 final function offsetGet($offset)
1283 return $this->$offset;
1286 final function offsetSet($offset, $value)
1288 $this->$offset = $value;
1291 final function offsetUnset($offset)
1293 unset($this->$offset);
1295 // }}}
1297 // REFERENCES {{{
1299 // array getReference() {{{
1301 * ActiveMongo extended the Mongo references, adding
1302 * the concept of 'dynamic' requests, saving in the database
1303 * the current query with its options (sort, limit, etc).
1305 * This is useful to associate a document with a given
1306 * request. To undestand this better please see the 'reference'
1307 * example.
1309 * @return array
1311 final function getReference($dynamic=FALSE)
1313 if (!$this->getID() && !$dynamic) {
1314 return NULL;
1317 $document = array(
1318 '$ref' => $this->CollectionName(),
1319 '$id' => $this->getID(),
1320 '$db' => $this->getDatabaseName(),
1321 'class' => get_class($this),
1324 if ($dynamic) {
1325 if (!$this->_cursor InstanceOf MongoCursor && $this->_cursor_ex === NULL) {
1326 $this->doQuery();
1329 if (!$this->_cursor InstanceOf MongoCursor) {
1330 throw new ActiveMongo_Exception("Only MongoDB native cursor could have dynamic references");
1333 $cursor = $this->_cursor;
1334 if (!is_callable(array($cursor, "Info"))) {
1335 throw new Exception("Please upgrade your PECL/Mongo module to use this feature");
1337 $document['dynamic'] = array();
1338 $query = $cursor->Info();
1339 foreach ($query as $type => $value) {
1340 $document['dynamic'][$type] = $value;
1343 return $document;
1345 // }}}
1347 // void getDocumentReferences($document, &$refs) {{{
1349 * Get Current References
1351 * Inspect the current document trying to get any references,
1352 * if any.
1354 * @param array $document Current document
1355 * @param array &$refs References found in the document.
1356 * @param array $parent_key Parent key
1358 * @return void
1360 final protected function getDocumentReferences($document, &$refs, $parent_key=NULL)
1362 foreach ($document as $key => $value) {
1363 if (is_array($value)) {
1364 if (MongoDBRef::isRef($value)) {
1365 $pkey = $parent_key;
1366 $pkey[] = $key;
1367 $refs[] = array('ref' => $value, 'key' => $pkey);
1368 } else {
1369 $parent_key1 = $parent_key;
1370 $parent_key1[] = $key;
1371 $this->getDocumentReferences($value, $refs, $parent_key1);
1376 // }}}
1378 // object _deferencingCreateObject(string $class) {{{
1380 * Called at deferencig time
1382 * Check if the given string is a class, and it is a sub class
1383 * of ActiveMongo, if it is instance and return the object.
1385 * @param string $class
1387 * @return object
1389 private function _deferencingCreateObject($class)
1391 if (!is_subclass_of($class, __CLASS__)) {
1392 throw new ActiveMongo_Exception("Fatal Error, imposible to create ActiveMongo object of {$class}");
1394 return new $class;
1396 // }}}
1398 // void _deferencingRestoreProperty(array &$document, array $keys, mixed $req) {{{
1400 * Called at deferencig time
1402 * This method iterates $document until it could match $keys path, and
1403 * replace its value by $req.
1405 * @param array &$document Document to replace
1406 * @param array $keys Path of property to change
1407 * @param mixed $req Value to replace.
1409 * @return void
1411 private function _deferencingRestoreProperty(&$document, $keys, $req)
1413 $obj = & $document;
1415 /* find the $req proper spot */
1416 foreach ($keys as $key) {
1417 $obj = & $obj[$key];
1420 $obj = $req;
1422 /* Delete reference variable */
1423 unset($obj);
1425 // }}}
1427 // object _deferencingQuery($request) {{{
1429 * Called at deferencig time
1431 * This method takes a dynamic reference and request
1432 * it to MongoDB.
1434 * @param array $request Dynamic reference
1436 * @return this
1438 private function _deferencingQuery($request)
1440 $collection = $this->_getCollection();
1441 $cursor = $collection->find($request['query'], $request['fields']);
1442 if ($request['limit'] > 0) {
1443 $cursor->limit($request['limit']);
1445 if ($request['skip'] > 0) {
1446 $cursor->skip($request['skip']);
1449 $this->setCursor($cursor);
1451 return $this;
1453 // }}}
1455 // void doDeferencing() {{{
1457 * Perform a deferencing in the current document, if there is
1458 * any reference.
1460 * ActiveMongo will do its best to group references queries as much
1461 * as possible, in order to perform as less request as possible.
1463 * ActiveMongo doesn't rely on MongoDB references, but it can support
1464 * it, but it is prefered to use our referencing.
1466 * @experimental
1468 final function doDeferencing($refs=array())
1470 /* Get current document */
1471 $document = get_document_vars($this);
1473 if (count($refs)==0) {
1474 /* Inspect the whole document */
1475 $this->getDocumentReferences($document, $refs);
1478 $db = $this->_getConnection();
1480 /* Gather information about ActiveMongo Objects
1481 * that we need to create
1483 $classes = array();
1484 foreach ($refs as $ref) {
1485 if (!isset($ref['ref']['class'])) {
1487 /* Support MongoDBRef, we do our best to be compatible {{{ */
1488 /* MongoDB 'normal' reference */
1490 $obj = MongoDBRef::get($db, $ref['ref']);
1492 /* Offset the current document to the right spot */
1493 /* Very inefficient, never use it, instead use ActiveMongo References */
1495 $this->_deferencingRestoreProperty($document, $ref['key'], $obj);
1497 /* Dirty hack, override our current document
1498 * property with the value itself, in order to
1499 * avoid replace a MongoDB reference by its content
1501 $this->_deferencingRestoreProperty($this->_current, $ref['key'], $obj);
1503 /* }}} */
1505 } else {
1507 if (isset($ref['ref']['dynamic'])) {
1508 /* ActiveMongo Dynamic Reference */
1510 /* Create ActiveMongo object */
1511 $req = $this->_deferencingCreateObject($ref['ref']['class']);
1513 /* Restore saved query */
1514 $req->_deferencingQuery($ref['ref']['dynamic']);
1516 $results = array();
1518 /* Add the result set */
1519 foreach ($req as $result) {
1520 $results[] = clone $result;
1523 /* add information about the current reference */
1524 foreach ($ref['ref'] as $key => $value) {
1525 $results[$key] = $value;
1528 $this->_deferencingRestoreProperty($document, $ref['key'], $results);
1530 } else {
1531 /* ActiveMongo Reference FTW! */
1532 $classes[$ref['ref']['class']][] = $ref;
1537 /* {{{ Create needed objects to query MongoDB and replace
1538 * our references by its objects documents.
1540 foreach ($classes as $class => $refs) {
1541 $req = $this->_deferencingCreateObject($class);
1543 /* Load list of IDs */
1544 $ids = array();
1545 foreach ($refs as $ref) {
1546 $ids[] = $ref['ref']['$id'];
1549 /* Search to MongoDB once for all IDs found */
1550 $req->find($ids);
1553 /* Replace our references by its objects */
1554 foreach ($refs as $ref) {
1555 $id = $ref['ref']['$id'];
1556 $place = $ref['key'];
1557 $req->rewind();
1558 while ($req->getID() != $id && $req->next());
1560 $this->_deferencingRestoreProperty($document, $place, clone $req);
1562 unset($obj);
1565 /* Release request, remember we
1566 * safely cloned it,
1568 unset($req);
1570 // }}}
1572 /* Replace the current document by the new deferenced objects */
1573 foreach ($document as $key => $value) {
1574 $this->$key = $value;
1577 // }}}
1579 // void getColumnDeference(&$document, $propety, ActiveMongo Obj) {{{
1581 * Prepare a "selector" document to search treaing the property
1582 * as a reference to the given ActiveMongo object.
1585 final function getColumnDeference(&$document, $property, ActiveMongo $obj)
1587 $document["{$property}.\$id"] = $obj->getID();
1589 // }}}
1591 // void findReferences(&$document) {{{
1593 * Check if in the current document to insert or update
1594 * exists any references to other ActiveMongo Objects.
1596 * @return void
1598 final function findReferences(&$document)
1600 if (!is_array($document)) {
1601 return;
1603 foreach($document as &$value) {
1604 $parent_class = __CLASS__;
1605 if (is_array($value)) {
1606 if (MongoDBRef::isRef($value)) {
1607 /* If the property we're inspecting is a reference,
1608 * we need to remove the values, restoring the valid
1609 * Reference.
1611 $arr = array(
1612 '$ref'=>1, '$id'=>1, '$db'=>1, 'class'=>1, 'dynamic'=>1
1614 foreach (array_keys($value) as $key) {
1615 if (!isset($arr[$key])) {
1616 unset($value[$key]);
1619 } else {
1620 $this->findReferences($value);
1622 } else if ($value InstanceOf $parent_class) {
1623 $value = $value->getReference();
1626 /* trick: delete last var. reference */
1627 unset($value);
1629 // }}}
1631 // void __clone() {{{
1632 /**
1633 * Cloned objects are rarely used, but ActiveMongo
1634 * uses it to create different objects per everyrecord,
1635 * which is used at deferencing. Therefore cloned object
1636 * do not contains the recordset, just the actual document,
1637 * so iterations are not allowed.
1640 final function __clone()
1642 if (!$this->_current) {
1643 throw new ActiveMongo_Exception("Empty objects can't be cloned");
1645 unset($this->_cursor);
1646 $this->_cloned = TRUE;
1648 // }}}
1650 // }}}
1652 // GET DOCUMENT ID {{{
1654 // getID() {{{
1656 * Return the current document ID. If there is
1657 * no document it would return FALSE.
1659 * @return object|FALSE
1661 final public function getID()
1663 if ($this->_id instanceof MongoID) {
1664 return $this->_id;
1666 return FALSE;
1668 // }}}
1670 // string key() {{{
1672 * Return the current key
1674 * @return string
1676 final function key()
1678 return (string)$this->getID();
1680 // }}}
1682 // }}}
1684 // Fancy (and silly) query abstraction {{{
1686 // _assertNotInQuery() {{{
1688 * Check if we can modify the query or not. We cannot modify
1689 * the query if we're iterating over and oldest query, in this case the
1690 * object must be reset.
1692 * @return void
1694 final private function _assertNotInQuery()
1696 if ($this->_cloned || $this->_cursor InstanceOf MongoCursor || $this->_cursor_ex != NULL) {
1697 throw new ActiveMongo_Exception("You cannot modify the query, please reset the object");
1700 // }}}
1702 // bool servedFromCache() {{{
1704 * Return True if the current result
1705 * was provided by a before_query hook (aka cache)
1706 * or False if it was retrieved from MongoDB
1708 * @return bool
1710 final function servedFromCache()
1712 return $this->_cached;
1714 // }}}
1716 // doQuery() {{{
1718 * Build the current request and send it to MongoDB.
1720 * @return this
1722 final function doQuery($use_cache=TRUE)
1724 if ($this->_cursor_ex) {
1725 switch ($this->_cursor_ex) {
1726 case self::FIND_AND_MODIFY:
1727 $this->_cursor_ex_value = NULL;
1728 return;
1729 default:
1730 throw new ActiveMongo_Exception("Invalid _cursor_ex value");
1733 $this->_assertNotInQuery();
1735 $query = array(
1736 'collection' => $this->CollectionName(),
1737 'query' => (array)$this->_query,
1738 'properties' => (array)$this->_properties,
1739 'sort' => (array)$this->_sort,
1740 'skip' => $this->_skip,
1741 'limit' => $this->_limit
1744 $this->_cached = FALSE;
1746 self::triggerEvent('before_query', array(&$query, &$documents, $use_cache));
1748 if ($documents InstanceOf MongoCursor && $use_cache) {
1749 $this->_cached = TRUE;
1750 $this->setCursor($documents);
1751 return $this;
1754 $col = $this->_getCollection();
1755 if (count($query['properties']) > 0) {
1756 $cursor = $col->find($query['query'], $query['properties']);
1757 } else {
1758 $cursor = $col->find($query['query']);
1760 if (count($query['sort']) > 0) {
1761 $cursor->sort($query['sort']);
1763 if ($query['limit'] > 0) {
1764 $cursor->limit($query['limit']);
1766 if ($query['skip'] > 0) {
1767 $cursor->skip($query['skip']);
1770 self::triggerEvent('after_query', array($query, $cursor));
1772 /* Our cursor must be sent to ActiveMongo */
1773 $this->setCursor($cursor);
1775 return $this;
1777 // }}}
1779 // properties($props) {{{
1781 * Select 'properties' or 'columns' to be included in the document,
1782 * by default all properties are included.
1784 * @param array $props
1786 * @return this
1788 final function properties($props)
1790 $this->_assertNotInQuery();
1792 if (!is_array($props) && !is_string($props)) {
1793 return FALSE;
1796 if (is_string($props)) {
1797 $props = explode(",", $props);
1800 foreach ($props as $id => $name) {
1801 $props[trim($name)] = 1;
1802 unset($props[$id]);
1806 /* _id should always be included */
1807 $props['_id'] = 1;
1809 $this->_properties = $props;
1811 return $this;
1814 final function columns($properties)
1816 return $this->properties($properties);
1818 // }}}
1820 // where($property, $value) {{{
1822 * Where abstraction.
1825 final function where($property_str, $value=NULL)
1827 $this->_assertNotInQuery();
1829 if (is_array($property_str)) {
1830 if ($value != NULL) {
1831 throw new ActiveMongo_Exception("Invalid parameters");
1833 foreach ($property_str as $property => $value) {
1834 if (is_numeric($property)) {
1835 $property = $value;
1836 $value = NULL;
1838 $this->where($property, $value);
1840 return $this;
1843 $column = explode(" ", trim($property_str));
1844 if (count($column) != 1 && count($column) != 2) {
1845 throw new ActiveMongo_Exception("Failed while parsing '{$property_str}'");
1846 } else if (count($column) == 2) {
1848 $exp_scalar = TRUE;
1849 switch (strtolower($column[1])) {
1850 case '>':
1851 case '$gt':
1852 $op = '$gt';
1853 break;
1855 case '>=':
1856 case '$gte':
1857 $op = '$gte';
1858 break;
1860 case '<':
1861 case '$lt':
1862 $op = '$lt';
1863 break;
1865 case '<=':
1866 case '$lte':
1867 $op = '$lte';
1868 break;
1870 case '==':
1871 case '$eq':
1872 case '=':
1873 if (is_array($value)) {
1874 $op = '$all';
1875 $exp_scalar = FALSE;
1876 } else {
1877 $op = NULL;
1879 break;
1881 case '!=':
1882 case '<>':
1883 case '$ne':
1884 if (is_array($value)) {
1885 $op = '$nin';
1886 $exp_scalar = FALSE;
1887 } else {
1888 $op = '$ne';
1890 break;
1892 case '%':
1893 case 'mod':
1894 case '$mod':
1895 $op = '$mod';
1896 $exp_scalar = FALSE;
1897 break;
1899 case 'exists':
1900 case '$exists':
1901 if ($value === NULL) {
1902 $value = 1;
1904 $op = '$exists';
1905 break;
1907 /* regexp */
1908 case 'regexp':
1909 case 'regex':
1910 $value = new MongoRegex($value);
1911 $op = NULL;
1912 break;
1914 /* arrays */
1915 case 'in':
1916 case '$in':
1917 $exp_scalar = FALSE;
1918 $op = '$in';
1919 break;
1921 case '$nin':
1922 case 'nin':
1923 $exp_scalar = FALSE;
1924 $op = '$nin';
1925 break;
1928 /* geo operations */
1929 case 'near':
1930 case '$near':
1931 $op = '$near';
1932 $exp_scalar = FALSE;
1933 break;
1935 default:
1936 throw new ActiveMongo_Exception("Failed to parse '{$column[1]}'");
1939 if ($exp_scalar && is_array($value)) {
1940 throw new ActiveMongo_Exception("Cannot use comparing operations with Array");
1941 } else if (!$exp_scalar && !is_array($value)) {
1942 throw new ActiveMongo_Exception("The operation {$column[1]} expected an Array");
1945 if ($op) {
1946 $value = array($op => $value);
1948 } else if (is_array($value)) {
1949 $value = array('$in' => $value);
1952 $spot = & $this->_query[$column[0]];
1953 if (is_array($spot) && is_array($value)) {
1954 $spot[key($value)] = current($value);
1955 } else {
1956 /* simulate AND among same properties if
1957 * multiple values is passed for same property
1959 if (isset($spot)) {
1960 if (is_array($spot)) {
1961 $spot['$all'][] = $value;
1962 } else {
1963 $spot = array('$all' => array($spot, $value));
1965 } else {
1966 $spot = $value;
1970 return $this;
1972 // }}}
1974 // sort($sort_str) {{{
1976 * Abstract the documents sorting.
1978 * @param string $sort_str List of properties to use as sorting
1980 * @return this
1982 final function sort($sort_str)
1984 $this->_assertNotInQuery();
1986 $this->_sort = array();
1987 foreach ((array)explode(",", $sort_str) as $sort_part_str) {
1988 $sort_part = explode(" ", trim($sort_part_str), 2);
1989 switch(count($sort_part)) {
1990 case 1:
1991 $sort_part[1] = 'ASC';
1992 break;
1993 case 2:
1994 break;
1995 default:
1996 throw new ActiveMongo_Exception("Don't know how to parse {$sort_part_str}");
1999 /* Columns name can't be empty */
2000 if (!trim($sort_part[0])) {
2001 throw new ActiveMongo_Exception("Don't know how to parse {$sort_part_str}");
2004 switch (strtoupper($sort_part[1])) {
2005 case 'ASC':
2006 $sort_part[1] = 1;
2007 break;
2008 case 'DESC':
2009 $sort_part[1] = -1;
2010 break;
2011 default:
2012 throw new ActiveMongo_Exception("Invalid sorting direction `{$sort_part[1]}`");
2014 $this->_sort[ $sort_part[0] ] = $sort_part[1];
2017 return $this;
2019 // }}}
2021 // limit($limit, $skip) {{{
2023 * Abstract the limitation and pagination of documents.
2025 * @param int $limit Number of max. documents to retrieve
2026 * @param int $skip Number of documents to skip
2028 * @return this
2030 final function limit($limit=0, $skip=0)
2032 $this->_assertNotInQuery();
2034 if ($limit < 0 || $skip < 0) {
2035 return FALSE;
2037 $this->_limit = $limit;
2038 $this->_skip = $skip;
2040 return $this;
2042 // }}}
2044 // FindAndModify(Array $document) {{{
2046 * findAndModify
2050 final function findAndModify($document)
2052 $this->_assertNotInQuery();
2054 if (count($document) === 0) {
2055 throw new ActiveMongo_Exception("Empty \$document is not allowed");
2058 $this->_cursor_ex = self::FIND_AND_MODIFY;
2059 $this->_findandmodify = $document;
2061 return $this;
2064 private function _execFindAndModify()
2066 $query = (array)$this->_query;
2068 $query = array(
2069 "findandmodify" => $this->CollectionName(),
2070 "query" => $query,
2071 "update" => array('$set' => $this->_findandmodify),
2072 "new" => TRUE,
2074 if (isset($this->_sort)) {
2075 $query["sort"] = $this->_sort;
2077 $this->_cursor_ex_value = $this->sendCMD($query);
2079 $this->_findandmodify_cnt++;
2081 // }}}
2083 // }}}
2085 // __sleep() {{{
2087 * Return a list of properties to serialize, to save
2088 * into MongoDB
2090 * @return array
2092 function __sleep()
2094 return array_keys(get_document_vars($this));
2096 // }}}
2100 require_once dirname(__FILE__)."/Validators.php";
2101 require_once dirname(__FILE__)."/Exceptions.php";
2104 * Local variables:
2105 * tab-width: 4
2106 * c-basic-offset: 4
2107 * End:
2108 * vim600: sw=4 ts=4 fdm=marker
2109 * vim<600: sw=4 ts=4