Added count() method
[activemongo.git] / ActiveMongo.php
blobf768a1ed0e822267f6ff56e0bd2c783d869fe086
1 <?php
2 /*
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 {{{
20 /**
21 * FilterException
23 * This is Exception is thrown if any validation
24 * fails when save() is called.
27 final class FilterException extends Exception
30 // }}}
32 // array get_object_vars_ex(stdobj $obj) {{{
33 /**
34 * Simple hack to avoid get private and protected variables
36 * @param obj
38 * @return array
40 function get_object_vars_ex($obj)
42 return get_object_vars($obj);
44 // }}}
47 /**
48 * ActiveMongo
50 * Simple ActiveRecord pattern built on top of MongoDB
52 * @author César D. Rodas <crodas@php.net>
55 abstract class ActiveMongo implements Iterator
58 // properties {{{
59 /**
60 * Current collections
62 * @type array
64 private static $_collections;
65 /**
66 * Current connection to MongoDB
68 * @type MongoConnection
70 private static $_conn;
71 /**
72 * Database name
74 * @type string
76 private static $_db;
77 /**
78 * Host name
80 * @type string
82 private static $_host;
83 /**
84 * Current document
86 * @type array
88 private $_current = array();
89 /**
90 * Result cursor
92 * @type MongoCursor
94 private $_cursor = null;
95 /**
96 * Number of total documents in this recordset
98 * @type int
99 */
100 private $_count = 0;
102 * Current document ID
104 * @type MongoID
106 private $_id;
107 // }}}
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
113 * a custom name.
115 * @return string Colleciton Name
117 protected function _getCollectionName()
119 return strtolower(get_class($this));
121 // }}}
123 // void connection($db, $host) {{{
125 * Connect
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
133 * @return void
135 final public static function connect($db, $host='localhost')
137 self::$_host = $host;
138 self::$_db = $db;
140 // }}}
142 // MongoConnection _getConnection() {{{
144 * Get Connection
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);
157 // }}}
159 // MongoCollection _getCollection() {{{
161 * Get Collection
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];
175 // }}}
178 // int count() {{{
180 * Return the number of documents in the actual request. If
181 * we're not in a request, it will return -1.
183 * @return int
185 function count()
187 if ($this->valid()) {
188 return $this->_count;
190 return -1;
192 // }}}
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
206 * @return array
208 final protected function getCurrentDocument($update=false)
210 $vars = array();
211 $current = (array)$this->_current;
212 $push = array();
213 $pull = array();
214 $unset = array();
215 $object = get_object_vars_ex($this);
217 foreach ($object as $key => $value) {
218 if (!$value) {
219 if ($update) {
220 $unset[$key] = 1;
222 continue;
225 if ($update) {
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));
239 if (!$filter) {
240 throw new FilterException("{$key} filter failed");
243 $vars[$key] = $value;
245 } else {
246 $filter = array($this, "{$key}_filter");
247 if (is_callable($filter)) {
248 $filter = call_user_func_array($filter, array(&$value, null));
249 if (!$filter) {
250 throw new FilterException("{$key} filter failed");
253 $vars[$key] = $value;
257 /* Updated behaves in a diff. way */
258 if ($update) {
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) {
277 return array();
279 return $vars;
281 // }}}
283 // void setCursor(MongoCollection $obj) {{{
285 * Set Cursor
287 * This method receive a MongoCursor and make
288 * it iterable.
290 * @param MongoCursor $obj
292 * @return void
294 final protected function setCursor($obj)
296 $this->_cursor = $obj;
297 $this->_count = $obj->count();
298 if ($this->_count) {
299 $this->setResult($obj->getNext());
300 } else {
301 $this->setResult(array());
304 // }}}
306 // void setResult(Array $obj) {{{
308 * Set Result
310 * This method takes an document and copy it
311 * as properties in this object.
313 * @param Array $obj
315 * @return void
317 final protected function setResult($obj)
319 /* Unsetting previous results, if any */
320 foreach (array_keys($this->_current) as $key) {
321 unset($this->$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;
332 // }}}
334 // this find() {{{
336 * Simple find
338 * Really simple find, which uses this object properties
339 * for fast filtering
341 * @return object this
343 final function find()
345 $vars = $this->getCurrentDocument();
346 $res = $this->_getCollection()->find($vars);
347 $this->setCursor($res);
348 return $this;
350 // }}}
352 // void save(bool $async) {{{
354 * Save
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
363 * @param bool $async
365 * @return void
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 */
375 /* PRE-save hook */
376 $this->pre_save($update ? 'update' : 'create', $obj);
377 if ($update) {
378 $conn->update(array('_id' => $this->_id), $obj);
379 $conn->save($obj);
380 foreach ($obj as $key => $value) {
381 $this->_current[$key] = $value;
383 } else {
384 $conn->insert($obj, $async);
385 $this->_id = $obj['_id'];
386 $this->_current = $obj;
388 /* post-save hook */
389 $this->on_save();
391 // }}}
393 // bool delete() {{{
395 * Delete the current document
397 * @return bool
399 final function delete()
401 if ($this->valid()) {
402 return $this->_getCollection()->remove(array('_id' => $this->_id));
404 return false;
406 // }}}
408 // void drop() {{{
410 * Delete the current colleciton and all its documents
412 * @return void
414 final function drop()
416 $this->_getCollection()->drop();
417 $this->setResult(array());
418 $this->_cursor = null;
420 // }}}
422 // bool valid() {{{
424 * Valid
426 * Return if we're on an iteration and if it is still valid
428 * @return true
430 final function valid()
432 return $this->_cursor InstanceOf MongoCursor && $this->_cursor->valid();
434 // }}}
436 // bool next() {{{
438 * Move to the next document
440 * @return bool
442 final function next()
444 return $this->_cursor->next();
446 // }}}
448 // this current() {{{
450 * Return the current object, and load the current document
451 * as this object property
453 * @return object
455 final function current()
457 $this->setResult($this->_cursor->current());
458 return $this;
460 // }}}
462 // bool rewind() {{{
464 * Go to the first document
466 final function rewind()
468 return $this->_cursor->rewind();
470 // }}}
472 // string key() {{{
474 * Return the current key
476 * @return string
478 final function key()
480 return $this->_cursor->key();
482 // }}}
484 // void pre_save($action, & $document) {{{
486 * PRE-save Hook,
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
491 * the insert.
494 * @param string $action Update or Create
495 * @param array &$document Document that will be sent to MongoDB.
497 * @return void
499 protected function pre_save($action, Array &$document)
502 // }}}
504 // void on_save() {{{
506 * On Save hook
508 * This method is fired right after an insert is performed.
510 * @return void
512 protected function on_save()
515 // }}}
517 // void on_iterate() {{{
519 * On Iterate Hook
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
523 * documents.
525 * @return void
527 protected function on_iterate()
530 // }}}
535 * Local variables:
536 * tab-width: 4
537 * c-basic-offset: 4
538 * End:
539 * vim600: sw=4 ts=4 fdm=marker
540 * vim<600: sw=4 ts=4