3 +----------------------------------------------------------------------+
4 | Copyright (c) 2009 The PHP Group |
5 +----------------------------------------------------------------------+
6 | This source file is subject to version 3.0 of the PHP license, |
7 | that is bundled with this package in the file LICENSE, and is |
8 | available through the world-wide-web at the following url: |
9 | http://www.php.net/license/3_0.txt. |
10 | If you did not receive a copy of the PHP license and are unable to |
11 | obtain it through the world-wide-web, please send a note to |
12 | license@php.net so we can mail you a copy immediately. |
13 +----------------------------------------------------------------------+
14 | Authors: Cesar Rodas <crodas@php.net> |
15 +----------------------------------------------------------------------+
19 // Class FilterException {{{
23 * This is Exception is thrown if any validation
24 * fails when save() is called.
27 final class FilterException
extends Exception
32 // array get_object_vars_ex(stdobj $obj) {{{
34 * Simple hack to avoid get private and protected variables
40 function get_object_vars_ex($obj)
42 return get_object_vars($obj);
50 * Simple ActiveRecord pattern built on top of MongoDB
52 * @author César D. Rodas <crodas@php.net>
55 abstract class ActiveMongo
implements Iterator
64 private static $_collections;
66 * Current connection to MongoDB
68 * @type MongoConnection
70 private static $_conn;
82 private static $_host;
88 private $_current = array();
94 private $_cursor = null;
96 * Number of total documents in this recordset
102 * Current document ID
109 // string _getCollectionName() {{{
111 * Get Collection Name, by default the class name,
112 * but you it can be override at the class itself to give
115 * @return string Colleciton Name
117 protected function _getCollectionName()
119 return strtolower(get_class($this));
123 // void connection($db, $host) {{{
127 * This method setup parameters to connect to a MongoDB
128 * database. The connection is done when it is needed.
130 * @param string $db Database name
131 * @param string $host Host to connect
135 final public static function connect($db, $host='localhost')
137 self
::$_host = $host;
142 // MongoConnection _getConnection() {{{
146 * Get a valid database connection
148 * @return MongoConnection
150 final protected static function _getConnection()
152 if (is_null(self
::$_conn)) {
153 self
::$_conn = new Mongo(self
::$_host);
155 return self
::$_conn->selectDB(self
::$_db);
159 // MongoCollection _getCollection() {{{
163 * Get a collection connection.
165 * @return MongoCollection
167 final protected function _getCollection()
169 $colName = $this->_getCollectionName();
170 if (!isset(self
::$_collections[$colName])) {
171 self
::$_collections[$colName] = self
::_getConnection()->selectCollection($colName);
173 return self
::$_collections[$colName];
180 * Return the number of documents in the actual request. If
181 * we're not in a request, it will return -1.
187 if ($this->valid()) {
188 return $this->_count
;
194 // array getCurrentDocument(bool $update) {{{
196 * Get Current Document
198 * Based on this object properties a new document (Array)
199 * is returned. If we're modifying an document, just the modified
200 * properties are included in this document, which uses $set,
201 * $unset, $pushAll and $pullAll.
204 * @param bool $update
208 final protected function getCurrentDocument($update=false)
211 $current = (array)$this->_current
;
215 $object = get_object_vars_ex($this);
217 foreach ($object as $key => $value) {
226 if (is_array($value) && isset($current[$key])) {
227 $toPush = array_diff($value, $current[$key]);
228 $toPull = array_diff($current[$key], $value);
229 if (count($toPush) > 0) {
230 $push[$key] = array_values($toPush);
232 if (count($toPull) > 0) {
233 $pull[$key] = array_values($toPull);
235 } else if(!isset($current[$key]) ||
$value !== $current[$key]) {
236 $filter = array($this, "{$key}_filter");
237 if (is_callable($filter)) {
238 $filter = call_user_func_array($filter, array(&$value, isset($current[$key]) ?
$current[$key] : null));
240 throw new FilterException("{$key} filter failed");
243 $vars[$key] = $value;
246 $filter = array($this, "{$key}_filter");
247 if (is_callable($filter)) {
248 $filter = call_user_func_array($filter, array(&$value, null));
250 throw new FilterException("{$key} filter failed");
253 $vars[$key] = $value;
257 /* Updated behaves in a diff. way */
259 foreach (array_diff(array_keys($this->_current
), array_keys($object)) as $property) {
260 $unset[$property] = 1;
262 if (count($vars) > 0) {
263 $vars = array('$set' => $vars);
265 if (count($push) > 0) {
266 $vars['$pushAll'] = $push;
268 if (count($pull) > 0) {
269 $vars['$pullAll'] = $pull;
271 if (count($unset) > 0) {
272 $vars['$unset'] = $unset;
276 if (count($vars) == 0) {
283 // void setCursor(MongoCollection $obj) {{{
287 * This method receive a MongoCursor and make
290 * @param MongoCursor $obj
294 final protected function setCursor($obj)
296 $this->_cursor
= $obj;
297 $this->_count
= $obj->count();
299 $this->setResult($obj->getNext());
301 $this->setResult(array());
306 // void setResult(Array $obj) {{{
310 * This method takes an document and copy it
311 * as properties in this object.
317 final protected function setResult($obj)
319 /* Unsetting previous results, if any */
320 foreach (array_keys($this->_current
) as $key) {
324 /* Add our current resultset as our object's property */
325 foreach ($obj as $key => $value) {
326 $this->$key = $value;
329 /* Save our record */
330 $this->_current
= $obj;
338 * Really simple find, which uses this object properties
341 * @return object this
343 final function find()
345 $vars = $this->getCurrentDocument();
346 $res = $this->_getCollection()->find($vars);
347 $this->setCursor($res);
352 // void save(bool $async) {{{
356 * This method save the current document in MongoDB. If
357 * we're modifying a document, a update is performed, otherwise
358 * the document is inserted.
360 * On updates, special operations such as $set, $pushAll, $pullAll
361 * and $unset in order to perform efficient updates
367 final function save($async=true)
369 $update = isset($this->_id
) && $this->_id
InstanceOf MongoID
;
370 $conn = $this->_getCollection();
371 $obj = $this->getCurrentDocument($update);
372 if (count($obj) == 0) {
373 return; /*nothing to do */
376 $this->pre_save($update ?
'update' : 'create', $obj);
378 $conn->update(array('_id' => $this->_id
), $obj);
380 foreach ($obj as $key => $value) {
381 $this->_current
[$key] = $value;
384 $conn->insert($obj, $async);
385 $this->_id
= $obj['_id'];
386 $this->_current
= $obj;
395 * Delete the current document
399 final function delete()
401 if ($this->valid()) {
402 return $this->_getCollection()->remove(array('_id' => $this->_id
));
410 * Delete the current colleciton and all its documents
414 final function drop()
416 $this->_getCollection()->drop();
417 $this->setResult(array());
418 $this->_cursor
= null;
426 * Return if we're on an iteration and if it is still valid
430 final function valid()
432 return $this->_cursor
InstanceOf MongoCursor
&& $this->_cursor
->valid();
438 * Move to the next document
442 final function next()
444 return $this->_cursor
->next();
448 // this current() {{{
450 * Return the current object, and load the current document
451 * as this object property
455 final function current()
457 $this->setResult($this->_cursor
->current());
464 * Go to the first document
466 final function rewind()
468 return $this->_cursor
->rewind();
474 * Return the current key
480 return $this->_cursor
->key();
484 // void pre_save($action, & $document) {{{
488 * This method is fired just before an insert or updated. The document
489 * is passed by reference, so it can be modified. Also if for instance
490 * one property is missing an Exception could be thrown to avoid
494 * @param string $action Update or Create
495 * @param array &$document Document that will be sent to MongoDB.
499 protected function pre_save($action, Array &$document)
504 // void on_save() {{{
508 * This method is fired right after an insert is performed.
512 protected function on_save()
517 // void on_iterate() {{{
521 * This method is fired right after a new document is loaded
522 * from the recorset, it could be useful to load references to other
527 protected function on_iterate()
539 * vim600: sw=4 ts=4 fdm=marker