- Merged lib/Events.php with lib/ActiveMongo.php
[activemongo.git] / lib / ActiveMongo.php
blobbeeb25a99a6c91c093015768a835ad32977b1646
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 // Class FilterException {{{
39 /**
40 * FilterException
42 * This is Exception is thrown if any validation
43 * fails when save() is called.
46 class ActiveMongo_FilterException extends Exception
49 // }}}
51 // array get_object_vars_ex(stdobj $obj) {{{
52 /**
53 * Simple hack to avoid get private and protected variables
55 * @param obj
57 * @return array
59 function get_object_vars_ex($obj)
61 return get_object_vars($obj);
63 // }}}
65 /**
66 * ActiveMongo
68 * Simple ActiveRecord pattern built on top of MongoDB. This class
69 * aims to provide easy iteration, data validation before update,
70 * and efficient update.
72 * @author César D. Rodas <crodas@php.net>
73 * @license PHP License
74 * @package ActiveMongo
75 * @version 1.0
78 abstract class ActiveMongo implements Iterator
81 // properties {{{
82 /**
83 * Current databases objects
85 * @type array
87 private static $_dbs;
88 /**
89 * Current collections objects
91 * @type array
93 private static $_collections;
94 /**
95 * Current connection to MongoDB
97 * @type MongoConnection
99 private static $_conn;
101 * Database name
103 * @type string
105 private static $_db;
107 * List of events handlers
109 * @type array
111 static private $_events = array();
113 * List of global events handlers
115 * @type array
117 static private $_super_events = array();
119 * Host name
121 * @type string
123 private static $_host;
125 * Current document
127 * @type array
129 private $_current = array();
131 * Result cursor
133 * @type MongoCursor
135 private $_cursor = null;
137 * Current document ID
139 * @type MongoID
141 private $_id;
144 * Tell if the current object
145 * is cloned or not.
147 * @type bool
149 private $_cloned = false;
150 // }}}
152 // GET CONNECTION CONFIG {{{
154 // string getCollectionName() {{{
156 * Get Collection Name, by default the class name,
157 * but you it can be override at the class itself to give
158 * a custom name.
160 * @return string Collection Name
162 protected function getCollectionName()
164 return strtolower(get_class($this));
166 // }}}
168 // string getDatabaseName() {{{
170 * Get Database Name, by default it is used
171 * the db name set by ActiveMong::connect()
173 * @return string DB Name
175 protected function getDatabaseName()
177 if (is_null(self::$_db)) {
178 throw new MongoException("There is no information about the default DB name");
180 return self::$_db;
182 // }}}
184 // void install() {{{
186 * Install.
188 * This static method iterate over the classes lists,
189 * and execute the setup() method on every ActiveMongo
190 * subclass. You should do this just once.
193 final public static function install()
195 $classes = array_reverse(get_declared_classes());
196 foreach ($classes as $class)
198 if ($class == __CLASS__) {
199 break;
201 if (is_subclass_of($class, __CLASS__)) {
202 $obj = new $class;
203 $obj->setup();
207 // }}}
209 // void connection($db, $host) {{{
211 * Connect
213 * This method setup parameters to connect to a MongoDB
214 * database. The connection is done when it is needed.
216 * @param string $db Database name
217 * @param string $host Host to connect
219 * @return void
221 final public static function connect($db, $host='localhost')
223 self::$_host = $host;
224 self::$_db = $db;
226 // }}}
228 // MongoConnection _getConnection() {{{
230 * Get Connection
232 * Get a valid database connection
234 * @return MongoConnection
236 final protected function _getConnection()
238 if (is_null(self::$_conn)) {
239 if (is_null(self::$_host)) {
240 self::$_host = 'localhost';
242 self::$_conn = new Mongo(self::$_host);
244 $dbname = $this->getDatabaseName();
245 if (!isSet(self::$_dbs[$dbname])) {
246 self::$_dbs[$dbname] = self::$_conn->selectDB($dbname);
248 return self::$_dbs[$dbname];
250 // }}}
252 // MongoCollection _getCollection() {{{
254 * Get Collection
256 * Get a collection connection.
258 * @return MongoCollection
260 final protected function _getCollection()
262 $colName = $this->getCollectionName();
263 if (!isset(self::$_collections[$colName])) {
264 self::$_collections[$colName] = self::_getConnection()->selectCollection($colName);
266 return self::$_collections[$colName];
268 // }}}
270 // }}}
272 // GET DOCUMENT TO SAVE OR UPDATE {{{
274 // bool getCurrentSubDocument(array &$document, string $parent_key, array $values, array $past_values) {{{
276 * Generate Sub-document
278 * This method build the difference between the current sub-document,
279 * and the origin one. If there is no difference, it would do nothing,
280 * otherwise it would build a document containing the differences.
282 * @param array &$document Document target
283 * @param string $parent_key Parent key name
284 * @param array $values Current values
285 * @param array $past_values Original values
287 * @return false
289 final function getCurrentSubDocument(&$document, $parent_key, Array $values, Array $past_values)
292 * The current property is a embedded-document,
293 * now we're looking for differences with the
294 * previous value (because we're on an update).
296 * It behaves exactly as getCurrentDocument,
297 * but this is simples (it doesn't support
298 * yet filters)
300 foreach ($values as $key => $value) {
301 $super_key = "{$parent_key}.{$key}";
302 if (is_array($value)) {
304 * Inner document detected
306 if (!isset($past_values[$key]) || !is_array($past_values[$key])) {
308 * We're lucky, it is a new sub-document,
309 * we simple add it
311 $document['$set'][$super_key] = $value;
312 } else {
314 * This is a document like this, we need
315 * to find out the differences to avoid
316 * network overhead.
318 if (!$this->getCurrentSubDocument($document, $super_key, $value, $past_values[$key])) {
319 return false;
322 continue;
323 } else if (!isset($past_values[$key]) || $past_values[$key] != $value) {
324 $document['$set'][$super_key] = $value;
328 foreach (array_diff(array_keys($past_values), array_keys($values)) as $key) {
329 $super_key = "{$parent_key}.{$key}";
330 $document['$unset'][$super_key] = 1;
333 return true;
335 // }}}
337 // array getCurrentDocument(bool $update) {{{
339 * Get Current Document
341 * Based on this object properties a new document (Array)
342 * is returned. If we're modifying an document, just the modified
343 * properties are included in this document, which uses $set,
344 * $unset, $pushAll and $pullAll.
347 * @param bool $update
349 * @return array
351 final protected function getCurrentDocument($update=false, $current=false)
353 $document = array();
354 $object = get_object_vars_ex($this);
356 if (!$current) {
357 $current = (array)$this->_current;
361 $this->findReferences($object);
363 $this->triggerEvent('before_validate_'.($update?'update':'creation'), array(&$object));
364 $this->triggerEvent('before_validate', array(&$object));
366 foreach ($object as $key => $value) {
367 if (!$value) {
368 continue;
370 if ($update) {
371 if (is_array($value) && isset($current[$key])) {
373 * If the Field to update is an array, it has a different
374 * behaviour other than $set and $unset. Fist, we need
375 * need to check if it is an array or document, because
376 * they can't be mixed.
379 if (!is_array($current[$key])) {
381 * We're lucky, the field wasn't
382 * an array previously.
384 $this->runFilter($key, $value, $current[$key]);
385 $document['$set'][$key] = $value;
386 continue;
389 if (!$this->getCurrentSubDocument($document, $key, $value, $current[$key])) {
390 throw new Exception("{$key}: Array and documents are not compatible");
392 } else if(!isset($current[$key]) || $value !== $current[$key]) {
394 * It is 'linear' field that has changed, or
395 * has been modified.
397 $past_value = isset($current[$key]) ? $current[$key] : null;
398 $this->runFilter($key, $value, $past_value);
399 $document['$set'][$key] = $value;
401 } else {
403 * It is a document insertation, so we
404 * create the document.
406 $this->runFilter($key, $value, null);
407 $document[$key] = $value;
411 /* Updated behaves in a diff. way */
412 if ($update) {
413 foreach (array_diff(array_keys($this->_current), array_keys($object)) as $property) {
414 if ($property == '_id') {
415 continue;
417 $document['$unset'][$property] = 1;
421 if (count($document) == 0) {
422 return array();
425 $this->triggerEvent('after_validate_'.($update?'update':'creation'), array(&$object));
426 $this->triggerEvent('after_validate', array(&$document));
428 return $document;
430 // }}}
432 // }}}
434 // EVENT HANDLERS {{{
436 // addEvent($action, $callback) {{{
438 * addEvent
441 final static function addEvent($action, $callback)
443 if (!is_callable($callback)) {
444 throw new Exception("Invalid callback");
447 $class = get_called_class();
448 if ($class == __CLASS__) {
449 $events = & self::$_super_events;
450 } else {
451 $events = & self::$_events[$class];
453 if (!isset($events[$action])) {
454 $events[$action] = array();
456 $events[$action][] = $callback;
457 return true;
459 // }}}
461 // triggerEvent(string $event, Array $events_params) {{{
462 final function triggerEvent($event, Array $events_params = array())
464 $events = & self::$_events[get_class($this)][$event];
465 $sevents = & self::$_super_events[$event];
467 if (!is_array($events_params)) {
468 return false;
471 /* Super-Events handler receives the ActiveMongo class name as first param */
472 $sevents_params = array_merge(array(get_class($this)), $events_params);
474 foreach (array('events', 'sevents') as $event_type) {
475 if (count($$event_type) > 0) {
476 $params = "{$event_type}_params";
477 foreach ($$event_type as $fnc) {
478 call_user_func_array($fnc, $$params);
483 /* Some natives events are allowed to be called
484 * as methods, if they exists
486 switch ($event) {
487 case 'before_create':
488 case 'before_update':
489 case 'before_validate':
490 case 'before_delete':
491 case 'after_create':
492 case 'after_update':
493 case 'after_validate':
494 case 'after_delete':
495 $fnc = array($this, $event);
496 $params = "events_params";
497 if (is_callable($fnc)) {
498 call_user_func_array($fnc, $$params);
500 break;
503 // }}}
505 // void runFilter(string $key, mixed &$value, mixed $past_value) {{{
507 * *Internal Method*
509 * This method check if the current document property has
510 * a filter method, if so, call it.
512 * If the filter returns false, throw an Exception.
514 * @return void
516 protected function runFilter($key, &$value, $past_value)
518 $filter = array($this, "{$key}_filter");
519 if (is_callable($filter)) {
520 $filter = call_user_func_array($filter, array(&$value, $past_value));
521 if ($filter===false) {
522 throw new ActiveMongo_FilterException("{$key} filter failed");
524 $this->$key = $value;
527 // }}}
529 // }}}
531 // void setCursor(MongoCursor $obj) {{{
533 * Set Cursor
535 * This method receive a MongoCursor and make
536 * it iterable.
538 * @param MongoCursor $obj
540 * @return void
542 final protected function setCursor(MongoCursor $obj)
544 $this->_cursor = $obj;
545 $this->setResult($obj->getNext());
547 // }}}
549 // void setResult(Array $obj) {{{
551 * Set Result
553 * This method takes an document and copy it
554 * as properties in this object.
556 * @param Array $obj
558 * @return void
560 final protected function setResult($obj)
562 /* Unsetting previous results, if any */
563 foreach (array_keys((array)$this->_current) as $key) {
564 unset($this->$key);
567 /* Add our current resultset as our object's property */
568 foreach ((array)$obj as $key => $value) {
569 if ($key[0] == '$') {
570 continue;
572 $this->$key = $value;
575 /* Save our record */
576 $this->_current = $obj;
578 // }}}
580 // this find([$_id]) {{{
582 * Simple find.
584 * Really simple find, which uses this object properties
585 * for fast filtering
587 * @return object this
589 final function find($_id = null)
591 $vars = get_object_vars_ex($this);
592 foreach ($vars as $key => $value) {
593 if (!$value) {
594 unset($vars[$key]);
596 $parent_class = __CLASS__;
597 if ($value InstanceOf $parent_class) {
598 $this->getColumnDeference($vars, $key, $value);
599 unset($vars[$key]); /* delete old value */
602 if ($_id != null) {
603 if (is_array($_id)) {
604 $vars['_id'] = array('$in' => $_id);
605 } else {
606 $vars['_id'] = $_id;
609 $res = $this->_getCollection()->find($vars);
610 $this->setCursor($res);
611 return $this;
613 // }}}
615 // void save(bool $async) {{{
617 * Save
619 * This method save the current document in MongoDB. If
620 * we're modifying a document, a update is performed, otherwise
621 * the document is inserted.
623 * On updates, special operations such as $set, $pushAll, $pullAll
624 * and $unset in order to perform efficient updates
626 * @param bool $async
628 * @return void
630 final function save($async=true)
632 $update = isset($this->_id) && $this->_id InstanceOf MongoID;
633 $conn = $this->_getCollection();
634 $obj = $this->getCurrentDocument($update);
635 if (count($obj) == 0) {
636 return; /*nothing to do */
639 /* PRE-save hook */
640 $this->triggerEvent('before_'.($update ? 'update' : 'create'), array(&$obj));
642 if ($update) {
643 $conn->update(array('_id' => $this->_id), $obj);
644 foreach ($obj as $key => $value) {
645 if ($key[0] == '$') {
646 continue;
648 $this->_current[$key] = $value;
650 } else {
651 $conn->insert($obj, $async);
652 $this->_id = $obj['_id'];
653 $this->_current = $obj;
656 $this->triggerEvent('after_'.($update ? 'update' : 'create'), array($obj));
658 // }}}
660 // bool delete() {{{
662 * Delete the current document
664 * @return bool
666 final function delete()
668 if ($this->valid()) {
669 $document = array('_id' => $this->_id);
670 $this->triggerEvent('before_delete', array($document));
671 $result = $this->_getCollection()->remove($document);
672 $this->triggerEvent('after_delete', array($document));
673 return $result;
675 return false;
677 // }}}
679 // void drop() {{{
681 * Delete the current colleciton and all its documents
683 * @return void
685 final static function drop()
687 $class = get_called_class();
688 if ($class == __CLASS__) {
689 return false;
691 $obj = new $class;
692 return $obj->_getCollection()->drop();
694 // }}}
696 // int count() {{{
698 * Return the number of documents in the actual request. If
699 * we're not in a request, it will return 0.
701 * @return int
703 final function count()
705 if ($this->valid()) {
706 return $this->_cursor->count();
708 return 0;
710 // }}}
712 // void setup() {{{
714 * This method should contain all the indexes, and shard keys
715 * needed by the current collection. This try to make
716 * installation on development environments easier.
718 function setup()
721 // }}}
723 // bool addIndex(array $columns, array $options) {{{
725 * addIndex
727 * Create an Index in the current collection.
729 * @param array $columns L ist of columns
730 * @param array $options Options
732 * @return bool
734 final function addIndex($columns, $options=array())
736 $default_options = array(
737 'background' => 1,
740 foreach ($default_options as $option => $value) {
741 if (!isset($options[$option])) {
742 $options[$option] = $value;
746 $collection = $this->_getCollection();
748 return $collection->ensureIndex($columns, $options);
750 // }}}
752 // string __toString() {{{
754 * To String
756 * If this object is treated as a string,
757 * it would return its ID.
759 * @return string
761 final function __toString()
763 return (string)$this->getID();
765 // }}}
767 // array sendCmd(array $cmd) {{{
769 * This method sends a command to the current
770 * database.
772 * @param array $cmd Current command
774 * @return array
776 final protected function sendCmd($cmd)
778 return $this->_getConnection()->command($cmd);
780 // }}}
782 // ITERATOR {{{
784 // void reset() {{{
786 * Reset our Object, delete the current cursor if any, and reset
787 * unsets the values.
789 * @return void
791 final function reset()
793 $this->_cursor = null;
794 $this->setResult(array());
796 // }}}
798 // bool valid() {{{
800 * Valid
802 * Return if we're on an iteration and if it is still valid
804 * @return true
806 final function valid()
808 return $this->_cursor InstanceOf MongoCursor && $this->_cursor->valid();
810 // }}}
812 // bool next() {{{
814 * Move to the next document
816 * @return bool
818 final function next()
820 if ($this->_cloned) {
821 throw new MongoException("Cloned objects can't iterate");
823 return $this->_cursor->next();
825 // }}}
827 // this current() {{{
829 * Return the current object, and load the current document
830 * as this object property
832 * @return object
834 final function current()
836 $this->setResult($this->_cursor->current());
837 return $this;
839 // }}}
841 // bool rewind() {{{
843 * Go to the first document
845 final function rewind()
847 return $this->_cursor->rewind();
849 // }}}
851 // }}}
853 // REFERENCES {{{
855 // array getReference() {{{
857 * ActiveMongo extended the Mongo references, adding
858 * the concept of 'dynamic' requests, saving in the database
859 * the current query with its options (sort, limit, etc).
861 * This is useful to associate a document with a given
862 * request. To undestand this better please see the 'reference'
863 * example.
865 * @return array
867 final function getReference($dynamic=false)
869 if (!$this->getID() && !$dynamic) {
870 return null;
873 $document = array(
874 '$ref' => $this->getCollectionName(),
875 '$id' => $this->getID(),
876 '$db' => $this->getDatabaseName(),
877 'class' => get_class($this),
880 if ($dynamic) {
881 $cursor = $this->_cursor;
882 if (!is_callable(array($cursor, "getQuery"))) {
883 throw new Exception("Please upgrade your PECL/Mongo module to use this feature");
885 $document['dynamic'] = array();
886 $query = $cursor->getQuery();
887 foreach ($query as $type => $value) {
888 $document['dynamic'][$type] = $value;
891 return $document;
893 // }}}
895 // void getDocumentReferences($document, &$refs) {{{
897 * Get Current References
899 * Inspect the current document trying to get any references,
900 * if any.
902 * @param array $document Current document
903 * @param array &$refs References found in the document.
904 * @param array $parent_key Parent key
906 * @return void
908 final protected function getDocumentReferences($document, &$refs, $parent_key=null)
910 foreach ($document as $key => $value) {
911 if (is_array($value)) {
912 if (MongoDBRef::isRef($value)) {
913 $pkey = $parent_key;
914 $pkey[] = $key;
915 $refs[] = array('ref' => $value, 'key' => $pkey);
916 } else {
917 $parent_key[] = $key;
918 $this->getDocumentReferences($value, $refs, $parent_key);
923 // }}}
925 // object _deferencingCreateObject(string $class) {{{
927 * Called at deferencig time
929 * Check if the given string is a class, and it is a sub class
930 * of ActiveMongo, if it is instance and return the object.
932 * @param string $class
934 * @return object
936 private function _deferencingCreateObject($class)
938 if (!is_subclass_of($class, __CLASS__)) {
939 throw new MongoException("Fatal Error, imposible to create ActiveMongo object of {$class}");
941 return new $class;
943 // }}}
945 // void _deferencingRestoreProperty(array &$document, array $keys, mixed $req) {{{
947 * Called at deferencig time
949 * This method iterates $document until it could match $keys path, and
950 * replace its value by $req.
952 * @param array &$document Document to replace
953 * @param array $keys Path of property to change
954 * @param mixed $req Value to replace.
956 * @return void
958 private function _deferencingRestoreProperty(&$document, $keys, $req)
960 $obj = & $document;
962 /* find the $req proper spot */
963 foreach ($keys as $key) {
964 $obj = & $obj[$key];
967 $obj = $req;
969 /* Delete reference variable */
970 unset($obj);
972 // }}}
974 // object _deferencingQuery($request) {{{
976 * Called at deferencig time
978 * This method takes a dynamic reference and request
979 * it to MongoDB.
981 * @param array $request Dynamic reference
983 * @return this
985 private function _deferencingQuery($request)
987 $collection = $this->_getCollection();
988 $cursor = $collection->find($request['query'], $request['fields']);
989 if ($request['limit'] > 0) {
990 $cursor->limit($request['limit']);
992 if ($request['skip'] > 0) {
993 $cursor->limit($request['limit']);
996 $this->setCursor($cursor);
998 return $this;
1000 // }}}
1002 // void doDeferencing() {{{
1004 * Perform a deferencing in the current document, if there is
1005 * any reference.
1007 * ActiveMongo will do its best to group references queries as much
1008 * as possible, in order to perform as less request as possible.
1010 * ActiveMongo doesn't rely on MongoDB references, but it can support
1011 * it, but it is prefered to use our referencing.
1013 * @experimental
1015 final function doDeferencing($refs=array())
1017 /* Get current document */
1018 $document = get_object_vars_ex($this);
1020 if (count($refs)==0) {
1021 /* Inspect the whole document */
1022 $this->getDocumentReferences($document, $refs);
1025 $db = $this->_getConnection();
1027 /* Gather information about ActiveMongo Objects
1028 * that we need to create
1030 $classes = array();
1031 foreach ($refs as $ref) {
1032 if (!isset($ref['ref']['class'])) {
1034 /* Support MongoDBRef, we do our best to be compatible {{{ */
1035 /* MongoDB 'normal' reference */
1037 $obj = MongoDBRef::get($db, $ref['ref']);
1039 /* Offset the current document to the right spot */
1040 /* Very inefficient, never use it, instead use ActiveMongo References */
1042 $this->_deferencingRestoreProperty($document, $ref['key'], clone $req);
1044 /* Dirty hack, override our current document
1045 * property with the value itself, in order to
1046 * avoid replace a MongoDB reference by its content
1048 $this->_deferencingRestoreProperty($this->_current, $ref['key'], clone $req);
1050 /* }}} */
1052 } else {
1054 if (isset($ref['ref']['dynamic'])) {
1055 /* ActiveMongo Dynamic Reference */
1057 /* Create ActiveMongo object */
1058 $req = $this->_deferencingCreateObject($ref['ref']['class']);
1060 /* Restore saved query */
1061 $req->_deferencingQuery($ref['ref']['dynamic']);
1063 $results = array();
1065 /* Add the result set */
1066 foreach ($req as $result) {
1067 $results[] = clone $result;
1070 /* add information about the current reference */
1071 foreach ($ref['ref'] as $key => $value) {
1072 $results[$key] = $value;
1075 $this->_deferencingRestoreProperty($document, $ref['key'], $results);
1077 } else {
1078 /* ActiveMongo Reference FTW! */
1079 $classes[$ref['ref']['class']][] = $ref;
1084 /* {{{ Create needed objects to query MongoDB and replace
1085 * our references by its objects documents.
1087 foreach ($classes as $class => $refs) {
1088 $req = $this->_deferencingCreateObject($class);
1090 /* Load list of IDs */
1091 $ids = array();
1092 foreach ($refs as $ref) {
1093 $ids[] = $ref['ref']['$id'];
1096 /* Search to MongoDB once for all IDs found */
1097 $req->find($ids);
1099 if ($req->count() != count($refs)) {
1100 $total = $req->count();
1101 $expected = count($refs);
1102 throw new MongoException("Dereferencing error, MongoDB replied {$total} objects, we expected {$expected}");
1105 /* Replace our references by its objects */
1106 foreach ($refs as $ref) {
1107 $id = $ref['ref']['$id'];
1108 $place = $ref['key'];
1109 $req->rewind();
1110 while ($req->getID() != $id && $req->next());
1112 assert($req->getID() == $id);
1114 $this->_deferencingRestoreProperty($document, $place, clone $req);
1116 unset($obj);
1119 /* Release request, remember we
1120 * safely cloned it,
1122 unset($req);
1124 // }}}
1126 /* Replace the current document by the new deferenced objects */
1127 foreach ($document as $key => $value) {
1128 $this->$key = $value;
1131 // }}}
1133 // void getColumnDeference(&$document, $propety, ActiveMongo Obj) {{{
1135 * Prepare a "selector" document to search treaing the property
1136 * as a reference to the given ActiveMongo object.
1139 final function getColumnDeference(&$document, $property, ActiveMongo $obj)
1141 $document["{$property}.\$id"] = $obj->getID();
1143 // }}}
1145 // void findReferences(&$document) {{{
1147 * Check if in the current document to insert or update
1148 * exists any references to other ActiveMongo Objects.
1150 * @return void
1152 final function findReferences(&$document)
1154 if (!is_array($document)) {
1155 return;
1157 foreach($document as &$value) {
1158 $parent_class = __CLASS__;
1159 if (is_array($value)) {
1160 if (MongoDBRef::isRef($value)) {
1161 /* If the property we're inspecting is a reference,
1162 * we need to remove the values, restoring the valid
1163 * Reference.
1165 $arr = array(
1166 '$ref'=>1, '$id'=>1, '$db'=>1, 'class'=>1, 'dynamic'=>1
1168 foreach (array_keys($value) as $key) {
1169 if (!isset($arr[$key])) {
1170 unset($value[$key]);
1173 } else {
1174 $this->findReferences($value);
1176 } else if ($value InstanceOf $parent_class) {
1177 $value = $value->getReference();
1180 /* trick: delete last var. reference */
1181 unset($value);
1183 // }}}
1185 // void __clone() {{{
1186 /**
1187 * Cloned objects are rarely used, but ActiveMongo
1188 * uses it to create different objects per everyrecord,
1189 * which is used at deferencing. Therefore cloned object
1190 * do not contains the recordset, just the actual document,
1191 * so iterations are not allowed.
1194 final function __clone()
1196 unset($this->_cursor);
1197 $this->_cloned = true;
1199 // }}}
1201 // }}}
1203 // GET DOCUMENT ID {{{
1205 // getID() {{{
1207 * Return the current document ID. If there is
1208 * no document it would return false.
1210 * @return object|false
1212 final public function getID()
1214 if ($this->_id instanceof MongoID) {
1215 return $this->_id;
1217 return false;
1219 // }}}
1221 // string key() {{{
1223 * Return the current key
1225 * @return string
1227 final function key()
1229 return $this->getID();
1231 // }}}
1233 // }}}
1236 require_once dirname(__FILE__)."/Validators.php";
1239 * Local variables:
1240 * tab-width: 4
1241 * c-basic-offset: 4
1242 * End:
1243 * vim600: sw=4 ts=4 fdm=marker
1244 * vim<600: sw=4 ts=4