diff --git a/lib/Doctrine/MongoDB/ArrayIterator.php b/lib/Doctrine/MongoDB/ArrayIterator.php new file mode 100644 index 00000000..91142adc --- /dev/null +++ b/lib/Doctrine/MongoDB/ArrayIterator.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\MongoDB; + +/** + * ArrayIterator + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @since 1.0 + * @author Jonathan H. Wage + */ +class ArrayIterator implements Iterator +{ + private $elements; + + public function __construct(array $elements = array()) + { + $this->elements = $elements; + } + + public function first() + { + return reset($this->elements); + } + + public function last() + { + return end($this->elements); + } + + public function key() + { + return key($this->elements); + } + + public function next() + { + next($this->elements); + } + + public function current() + { + return current($this->elements); + } + + public function count() + { + return count($this->elements); + } + + public function rewind() + { + reset($this->elements); + } + + public function reset() + { + reset($this->elements); + } + + public function valid() + { + return current($this->elements) !== false; + } + + public function toArray() + { + return $this->elements; + } + + public function getSingleResult() + { + $result = null; + $this->valid() ?: $this->next(); + if ($this->valid()) { + $result = $this->current(); + } + $this->reset(); + return $result ? $result : null; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Collection.php b/lib/Doctrine/MongoDB/Collection.php new file mode 100644 index 00000000..f0ffd8b9 --- /dev/null +++ b/lib/Doctrine/MongoDB/Collection.php @@ -0,0 +1,487 @@ +. + */ + +namespace Doctrine\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata, + Doctrine\Common\EventManager, + Doctrine\ODM\MongoDB\Mapping\Types\Type, + Doctrine\ODM\MongoDB\Event\CollectionEventArgs, + Doctrine\ODM\MongoDB\Event\CollectionUpdateEventArgs; + +/** + * Wrapper for the PHP MongoCollection class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class Collection +{ + /** + * The PHP MongoCollection being wrapped. + * + * @var \MongoCollection + */ + protected $mongoCollection; + + /** + * The Database instance this collection belongs to. + * + * @var Database + */ + protected $db; + + /** + * The event manager that is the central point of the event system. + * + * @var Doctrine\Common\EventManager + */ + protected $eventManager; + + /** + * A callable for logging statements. + * + * @var mixed + */ + protected $loggerCallable; + + /** + * Mongo command prefix + * + * @var string + */ + protected $cmd; + + /** + * Create a new MongoCollection instance that wraps a PHP MongoCollection instance + * for a given ClassMetadata instance. + * + * @param MongoCollection $mongoCollection The MongoCollection instance. + * @param Database $db The Database instance. + * @param EventManager $evm The EventManager instance. + * @param mixed $loggerCallable The logger callable. + */ + public function __construct(\MongoCollection $mongoCollection, Database $db, EventManager $evm, $loggerCallable, $cmd) + { + $this->mongoCollection = $mongoCollection; + $this->db = $db; + $this->eventManager = $evm; + $this->loggerCallable = $loggerCallable; + $this->cmd = $cmd; + } + + /** + * Log something using the configured logger callable. + * + * @param array $log The array of data to log. + */ + public function log(array $log) + { + $log['db'] = $this->db->getName(); + $log['collection'] = $this->getName(); + call_user_func_array($this->loggerCallable, array($log)); + } + + /** + * Returns the wrapped MongoCollection instance. + * + * @return \MongoCollection + */ + public function getMongoCollection() + { + return $this->mongoCollection; + } + + public function getDatabase() + { + return $this->db; + } + + /** @override */ + public function batchInsert(array &$a, array $options = array()) + { + if ($this->eventManager->hasListeners(Events::preBatchInsert)) { + $this->eventManager->dispatchEvent(Events::preBatchInsert, new CollectionEventArgs($this, $a)); + } + + $this->doBatchInsert($a, $options); + + if ($this->loggerCallable) { + $this->log(array( + 'batchInsert' => true, + 'num' => count($a), + 'data' => $a, + 'options' => $options + )); + } + + if ($this->eventManager->hasListeners(Events::postBatchInsert)) { + $this->eventManager->dispatchEvent(Events::postBatchInsert, new CollectionEventArgs($this, $result)); + } + + return $a; + } + + protected function doBatchInsert(array &$a, array $options = array()) + { + return $this->mongoCollection->batchInsert($a, $options); + } + + /** @override */ + public function update($criteria, array $newObj, array $options = array()) + { + if ($this->eventManager->hasListeners(Events::preUpdate)) { + $this->eventManager->dispatchEvent(Events::preUpdate, new CollectionUpdateEventArgs($this, $criteria, $newObj, $options)); + } + + if ($this->loggerCallable) { + $this->log(array( + 'update' => true, + 'criteria' => $criteria, + 'newObj' => $newObj, + 'options' => $options + )); + } + + $result = $this->doUpdate($criteria, $newObj, $options); + + if ($this->eventManager->hasListeners(Events::postUpdate)) { + $this->eventManager->dispatchEvent(Events::postUpdate, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + protected function doUpdate($criteria, array $newObj, array $options) + { + if (is_scalar($criteria)) { + $criteria = array('_id' => $criteria); + } + return $this->mongoCollection->update($criteria, $newObj, $options); + } + + /** @override */ + public function find(array $query = array(), array $fields = array()) + { + if ($this->eventManager->hasListeners(Events::preFind)) { + $this->eventManager->dispatchEvent(Events::preFind, new CollectionEventArgs($this, $query)); + } + + if ($this->loggerCallable) { + $this->log(array( + 'find' => true, + 'query' => $query, + 'fields' => $fields + )); + } + + $result = $this->doFind($query, $fields); + + if ($this->eventManager->hasListeners(Events::postFind)) { + $this->eventManager->dispatchEvent(Events::postFind, new CollectionEventArgs($this, $result)); + } + + return new Cursor($result); + } + + public function doFind(array $query, array $fields) + { + return $this->mongoCollection->find($query, $fields); + } + + /** @override */ + public function findOne(array $query = array(), array $fields = array()) + { + if ($this->eventManager->hasListeners(Events::preFindOne)) { + $this->eventManager->dispatchEvent(Events::preFindOne, new CollectionEventArgs($this, $query)); + } + + if ($this->loggerCallable) { + $this->log(array( + 'findOne' => true, + 'query' => $query, + 'fields' => $fields + )); + } + + $result = $this->doFindOne($query, $fields); + + if ($this->eventManager->hasListeners(Events::postFindOne)) { + $this->eventManager->dispatchEvent(Events::postFindOne, new CollectionEventArgs($this, $result)); + } + + return $result; + } + + protected function doFindOne(array $query, array $fields) + { + return $this->mongoCollection->findOne($query, $fields); + } + + public function findAndRemove(array $query, array $options = array()) + { + $command = array(); + $command['findandmodify'] = $this->mongoCollection->getName(); + $command['query'] = $query; + $command['remove'] = true; + $command['options'] = $options; + + $result = $this->db->command($command); + if (isset($result['value'])) { + $document = $result['value']; + if ($this->mongoCollection instanceof \MongoGridFS) { + // Remove the file data from the chunks collection + $this->mongoCollection->chunks->remove(array('files_id' => $document['_id']), $options); + } + return $document; + } + return null; + } + + public function findAndModify(array $query, array $newObj, array $options = array()) + { + $command = array(); + $command['findandmodify'] = $this->mongoCollection->getName(); + $command['query'] = $query; + $command['update'] = $newObj; + if (isset($options['upsert'])) { + $command['upsert'] = true; + unset($options['upsert']); + } + if (isset($options['new'])) { + $command['new'] = true; + unset($options['new']); + } + $command['options'] = $options; + $result = $this->db->command($command); + return $result['value']; + } + + /** @proxy */ + public function count(array $query = array(), $limit = 0, $skip = 0) + { + if ($this->loggerCallable) { + $this->log(array( + 'count' => true, + 'query' => $query, + 'limit' => $limit, + 'skip' => $skip + )); + } + + return $this->mongoCollection->count($query, $limit, $skip); + } + + /** @proxy */ + public function createDBRef(array $a) + { + if ($this->loggerCallable) { + $this->log(array( + 'createDBRef' => true, + 'reference' => $a + )); + } + + return $this->mongoCollection->createDBRef($a); + } + + /** @proxy */ + public function deleteIndex($keys) + { + if ($this->loggerCallable) { + $this->log(array( + 'deleteIndex' => true, + 'keys' => $keys + )); + } + + return $this->mongoCollection->deleteIndex($keys); + } + + /** @proxy */ + public function deleteIndexes() + { + if ($this->loggerCallable) { + $this->log(array( + 'deleteIndexes' => true + )); + } + + return $this->mongoCollection->deleteIndexes(); + } + + /** @proxy */ + public function drop() + { + if ($this->loggerCallable) { + $this->log(array( + 'drop' => true + )); + } + + return $this->mongoCollection->drop(); + } + + /** @proxy */ + public function ensureIndex(array $keys, array $options) + { + if ($this->loggerCallable) { + $this->log(array( + 'ensureIndex' => true, + 'keys' => $keys, + 'options' => $options + )); + } + + return $this->mongoCollection->ensureIndex($keys, $options); + } + + /** @proxy */ + public function __get($name) + { + return $this->mongoCollection->__get($name); + } + + /** @proxy */ + public function getDBRef(array $ref) + { + if ($this->loggerCallable) { + $this->log(array( + 'getDBRef' => true, + 'reference' => $ref + )); + } + + return $this->mongoCollection->getDBRef($ref); + } + + /** @proxy */ + public function getIndexInfo() + { + return $this->mongoCollection->getIndexInfo(); + } + + /** @proxy */ + public function getName() + { + return $this->mongoCollection->getName(); + } + + /** @proxy */ + public function group($keys, array $initial, $reduce, array $options = array()) + { + if ($this->loggerCallable) { + $this->log(array( + 'group' => true, + 'keys' => $keys, + 'initial' => $initial, + 'reduce' => $reduce, + 'options' => $options + )); + } + + $result = $this->mongoCollection->group($keys, $initial, $reduce, $options); + return new ArrayIterator($result); + } + + /** @proxy */ + public function insert(array &$a, array $options = array()) + { + if ($this->eventManager->hasListeners(Events::preInsert)) { + $this->eventManager->dispatchEvent(Events::preInsert, new CollectionEventArgs($this, $a)); + } + + $result = $this->doInsert($a, $options); + + if ($this->loggerCallable) { + $this->log(array( + 'insert' => true, + 'document' => $a, + 'options' => $options + )); + } + + if ($this->eventManager->hasListeners(Events::postInsert)) { + $this->eventManager->dispatchEvent(Events::postInsert, new CollectionEventArgs($this, $result)); + } + return $result; + } + + protected function doInsert(array &$a, array $options) + { + $docs = array(&$a); + return $this->mongoCollection->batchInsert($docs, $options); + } + + /** @proxy */ + public function remove(array $criteria, array $options = array()) + { + if ($this->loggerCallable) { + $this->log(array( + 'remove' => true, + 'criteria' => $criteria, + 'options' => $options + )); + } + + return $this->mongoCollection->remove($criteria, $options); + } + + /** @proxy */ + public function save(array &$a, array $options = array()) + { + $result = $this->doSave($a, $options); + + if ($this->loggerCallable) { + $this->log(array( + 'save' => true, + 'document' => $a, + 'options' => $options + )); + } + + return $result; + } + + protected function doSave(array &$a, array $options) + { + return $this->mongoCollection->save($a, $options); + } + + /** @proxy */ + public function validate($scanData = false) + { + if ($this->loggerCallable) { + $this->log(array( + 'validate' => true, + 'scanData' => $scanData + )); + } + + return $this->mongoCollection->validate($scanData); + } + + /** @proxy */ + public function __toString() + { + return $this->mongoCollection->__toString(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Configuration.php b/lib/Doctrine/MongoDB/Configuration.php new file mode 100644 index 00000000..0d905646 --- /dev/null +++ b/lib/Doctrine/MongoDB/Configuration.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\MongoDB; + +use Doctrine\ODM\MongoDB\Mapping\Driver\Driver, + Doctrine\ODM\MongoDB\Mapping\Driver\PHPDriver, + Doctrine\Common\Cache\Cache; + +/** + * Configuration + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +class Configuration +{ + /** + * Array of attributes for this configuration instance. + * + * @var array $attributes + */ + private $attributes = array('mongoCmd' => '$'); + + /** + * Set the logger callable. + * + * @param mixed $loggerCallable The logger callable. + */ + public function setLoggerCallable($loggerCallable) + { + $this->attributes['loggerCallable'] = $loggerCallable; + } + + /** + * Gets the logger callable. + * + * @return mixed $loggerCallable The logger callable. + */ + public function getLoggerCallable() + { + return isset($this->attributes['loggerCallable']) ? + $this->attributes['loggerCallable'] : null; + } + + /** + * Get mongodb command prefix - '$' by default + * @return string + */ + public function getMongoCmd() + { + return $this->attributes['mongoCmd']; + } + + /** + * Set mongodb command prefix + * @param string $cmd + */ + public function setMongoCmd($cmd) + { + $this->attributes['mongoCmd'] = $cmd; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Connection.php b/lib/Doctrine/MongoDB/Connection.php new file mode 100644 index 00000000..949b004f --- /dev/null +++ b/lib/Doctrine/MongoDB/Connection.php @@ -0,0 +1,204 @@ +. + */ + +namespace Doctrine\MongoDB; + +use Doctrine\Common\EventManager; + +/** + * Wrapper for the PHP Mongo class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class Connection +{ + /** The PHP Mongo instance. */ + private $mongo; + + /** The server string */ + private $server; + + /** The array of server options to use when connecting */ + private $options = array(); + + /** The Configuration instance */ + private $config; + + /** + * The event manager that is the central point of the event system. + * + * @var Doctrine\Common\EventManager + */ + private $eventManager; + + /** + * The logger callable. + * + * @var mixed + */ + private $loggerCallable; + + /** + * Mongo command prefix + * + * @var string + */ + private $cmd; + + /** + * Array holding selected databases. + * + * @var array + */ + private $databases = array(); + + /** + * Create a new Mongo wrapper instance. + * + * @param mixed $server A string server name, an existing Mongo instance or can be omitted. + * @param array $options + */ + public function __construct($server = null, array $options = array(), Configuration $config = null, EventManager $evm = null) + { + if ($server instanceof \Mongo) { + $this->mongo = $server; + } elseif ($server !== null) { + $this->server = $server; + $this->options = $options; + } + $this->config = $config ? $config : new Configuration(); + $this->eventManager = $evm ? $evm : new EventManager(); + $this->loggerCallable = $this->config->getLoggerCallable(); + $this->cmd = $this->config->getMongoCmd(); + } + + public function initialize() + { + if ($this->mongo === null) { + if ($this->server) { + $this->mongo = new \Mongo($this->server, $this->options); + } else { + $this->mongo = new \Mongo(); + } + } + } + + /** + * Log something using the configured logger callable. + * + * @param array $log The array of data to log. + */ + public function log(array $log) + { + call_user_func_array($this->loggerCallable, array($log)); + } + + /** + * Set the PHP Mongo instance to wrap. + * + * @param Mongo $mongo The PHP Mongo instance + */ + public function setMongo(\Mongo $mongo) + { + $this->initialize(); + $this->mongo = $mongo; + } + + /** + * Returns the PHP Mongo instance being wrapped. + * + * @return Mongo + */ + public function getMongo() + { + return $this->mongo; + } + + /** @proxy */ + public function close() + { + $this->initialize(); + return $this->mongo->close(); + } + + /** @proxy */ + public function connect() + { + $this->initialize(); + return $this->mongo->connect(); + } + + /** @proxy */ + public function connectUntil() + { + $this->initialize(); + return $this->mongo->connectUntil(); + } + + /** @proxy */ + public function dropDB($db) + { + $this->initialize(); + return $this->mongo->dropDB($db); + } + + /** @proxy */ + public function __get($key) + { + $this->initialize(); + return $this->mongo->$key; + } + + /** @proxy */ + public function listDBs() + { + $this->initialize(); + return $this->mongo->listDBs(); + } + + /** @proxy */ + public function selectCollection($db, $collection) + { + $this->initialize(); + return $this->selectDB($db)->selectCollection($collection); + } + + /** @proxy */ + public function selectDB($name) + { + if ( ! isset($this->databases[$name])) { + $this->initialize(); + $db = $this->mongo->selectDB($name); + $this->databases[$name] = new Database( + $db, $this->eventManager, $this->loggerCallable, $this->cmd + ); + } + return $this->databases[$name]; + } + + /** @proxy */ + public function __toString() + { + $this->initialize(); + return $this->mongo->__toString(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Cursor.php b/lib/Doctrine/MongoDB/Cursor.php new file mode 100644 index 00000000..73b9f1e3 --- /dev/null +++ b/lib/Doctrine/MongoDB/Cursor.php @@ -0,0 +1,243 @@ +. + */ + +namespace Doctrine\MongoDB; + +/** + * Wrapper for the PHP MongoCursor class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class Cursor implements Iterator +{ + /** The PHP MongoCursor being wrapped */ + private $mongoCursor; + + /** + * Create a new MongoCursor which wraps around a given PHP MongoCursor. + * + * @param MongoCursor $mongoCursor + */ + public function __construct(\MongoCursor $mongoCursor) + { + $this->mongoCursor = $mongoCursor; + } + + /** + * Returns the MongoCursor instance being wrapped. + * + * @return MongoCursor $mongoCursor The MongoCursor instance being wrapped. + */ + public function getMongoCursor() + { + return $this->mongoCursor; + } + + /** @proxy */ + public function current() + { + $current = $this->mongoCursor->current(); + if ($current instanceof \MongoGridFSFile) { + $document = $current->file; + $document['file'] = new GridFSFile($current); + $current = $document; + } + return $current; + } + + /** @proxy */ + public function key() + { + return $this->mongoCursor->key(); + } + + /** @proxy */ + public function dead() + { + return $this->mongoCursor->dead(); + } + + /** @proxy */ + public function explain() + { + return $this->mongoCursor->explain(); + } + + /** @proxy */ + public function fields(array $f) + { + $this->mongoCursor->fields($f); + return $this; + } + + /** @proxy */ + public function getNext() + { + $next = $this->mongoCursor->getNext(); + if ($next instanceof \MongoGridFSFile) { + $document = $next->file; + $document['file'] = new GridFSFile($next); + $next = $document; + } + return $next; + } + + /** @proxy */ + public function hasNext() + { + return $this->mongoCursor->hasNext(); + } + + /** @proxy */ + public function hint(array $keyPattern) + { + $this->mongoCursor->hint($keyPattern); + return $this; + } + + /** @proxy */ + public function immortal($liveForever = true) + { + $this->mongoCursor->immortal($liveForever); + return $this; + } + + /** @proxy */ + public function info() + { + return $this->mongoCursor->info(); + } + + /** @proxy */ + public function rewind() + { + return $this->mongoCursor->rewind(); + } + + /** @proxy */ + public function next() + { + return $this->mongoCursor->next(); + } + + /** @proxy */ + public function reset() + { + return $this->mongoCursor->reset(); + } + + /** @proxy */ + public function count($foundOnly = false) + { + return $this->mongoCursor->count($foundOnly); + } + + /** @proxy */ + public function addOption($key, $value) + { + $this->mongoCursor->addOption($key, $value); + return $this; + } + + /** @proxy */ + public function batchSize($num) + { + $htis->mongoCursor->batchSize($num); + return $this; + } + + /** @proxy */ + public function limit($num) + { + $this->mongoCursor->limit($num); + return $this; + } + + /** @proxy */ + public function skip($num) + { + $this->mongoCursor->skip($num); + return $this; + } + + /** @proxy */ + public function slaveOkay($okay = true) + { + $this->mongoCursor->slaveOkay($okay); + return $this; + } + + /** @proxy */ + public function snapshot() + { + $this->mongoCursor->snapshot(); + return $this; + } + + /** @proxy */ + public function sort($fields) + { + $this->mongoCursor->sort($fields); + return $this; + } + + /** @proxy */ + public function tailable($tail = true) + { + $this->mongoCursor->tailable($tail); + return $this; + } + + /** @proxy */ + public function timeout($ms) + { + $this->mongoCursor->timeout($ms); + return $this; + } + + /** @proxy */ + public function valid() + { + return $this->mongoCursor->valid(); + } + + public function toArray() + { + return iterator_to_array($this); + } + + /** + * Get the first single result from the cursor. + * + * @return object $document The single document. + */ + public function getSingleResult() + { + $result = null; + $this->valid() ?: $this->next(); + if ($this->valid()) { + $result = $this->current(); + } + $this->reset(); + return $result ? $result : null; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Database.php b/lib/Doctrine/MongoDB/Database.php new file mode 100644 index 00000000..dae8e27e --- /dev/null +++ b/lib/Doctrine/MongoDB/Database.php @@ -0,0 +1,307 @@ +. + */ + +namespace Doctrine\MongoDB; + +use Doctrine\Common\EventManager; + +/** + * Wrapper for the PHP MongoDB class. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 1.0 + * @author Jonathan H. Wage + */ +class Database +{ + /** The PHP MongoDB instance being wrapped */ + private $mongoDB; + + /** + * The event manager that is the central point of the event system. + * + * @var Doctrine\Common\EventManager + */ + private $eventManager; + + /** + * A callable for logging statements. + * + * @var mixed + */ + private $loggerCallable; + + /** + * Mongo command prefix + * + * @var string + */ + private $cmd; + + /** + * Array holding selected collections. + * + * @var array + */ + private $collections = array(); + + /** + * Array holding GridFS instances. + * + * @var GridFS + */ + private $gridFileSystems = array(); + + /** + * Create a new MongoDB instance which wraps a PHP MongoDB instance. + * + * @param MongoDB $mongoDB The MongoDB instance to wrap. + */ + public function __construct(\MongoDB $mongoDB, EventManager $evm, $loggerCallable, $cmd) + { + $this->mongoDB = $mongoDB; + $this->eventManager = $evm; + $this->loggerCallable = $loggerCallable; + $this->cmd = $cmd; + } + + /** + * Log something using the configured logger callable. + * + * @param array $log The array of data to log. + */ + public function log(array $log) + { + $log['db'] = $this->getName(); + call_user_func_array($this->loggerCallable, array($log)); + } + + /** + * Gets the name of this database + * + * @return string $name + */ + public function getName() + { + return $this->__toString(); + } + + /** + * Get the MongoDB instance being wrapped. + * + * @return MongoDB $mongoDB + */ + public function getMongoDB() + { + return $this->mongoDB; + } + + /** @proxy */ + public function authenticate($username, $password) + { + if ($this->loggerCallable) { + $this->log(array( + 'authenticate' => true, + 'username' => $username, + 'password' => $password + )); + } + + return $this->mongoDB->authenticate($username, $password); + } + + /** @proxy */ + public function command(array $data) + { + if ($this->loggerCallable) { + $this->log(array( + 'command' => true, + 'data' => $data + )); + } + + return $this->mongoDB->command($data); + } + + /** @proxy */ + public function createCollection($name, $capped = false, $size = 0, $max = 0) + { + if ($this->loggerCallable) { + $this->log(array( + 'createCollection' => true, + 'capped' => $capped, + 'size' => $size, + 'max' => $max + )); + } + + return $this->mongoDB->createCollection($name, $capped, $size, $max); + } + + /** @proxy */ + public function createDBRef($collection, $a) + { + if ($this->loggerCallable) { + $this->log(array( + 'createDBRef' => true, + 'collection' => $collection, + 'reference' => $a + )); + } + + return $this->mongoDB->createDBRef($collection, $a); + } + + /** @proxy */ + public function drop() + { + if ($this->loggerCallable) { + $this->log(array( + 'drop' => true + )); + } + + return $this->mongoDB->drop(); + } + + /** @proxy */ + public function dropCollection($coll) + { + if ($this->loggerCallable) { + $this->log(array( + 'dropCollection' => true, + 'collection' => $collection + )); + } + + return $this->mongoDB->dropCollection($coll); + } + + /** @proxy */ + public function execute($code, array $args = array()) + { + if ($this->loggerCallable) { + $this->log(array( + 'execute' => true, + 'code' => $code, + 'args' => $args + )); + } + + return $this->mongoDB->execute($code, $args); + } + + /** @proxy */ + public function forceError() + { + return $this->mongoDB->forceError(); + } + + /** @proxy */ + public function __get($name) + { + return $this->mongoDB->__get($name); + } + + /** @proxy */ + public function getDBRef(array $ref) + { + if ($this->loggerCallable) { + $this->log(array( + 'getDBRef' => true, + 'reference' => $ref + )); + } + + return $this->mongoDB->getDBRef($ref); + } + + /** @proxy */ + public function getGridFS($prefix = 'fs') + { + if ( ! isset($this->gridFileSystems[$prefix])) { + $gridFS = $this->mongoDB->getGridFS($prefix); + $this->gridFileSystems[$prefix] = new GridFS( + $gridFS, $this, $this->eventManager, $this->loggerCallable, $this->cmd + ); + } + return $this->gridFileSystems[$prefix]; + } + + /** @proxy */ + public function getProfilingLevel() + { + return $this->mongoDB->getProfilingLevel(); + } + + /** @proxy */ + public function lastError() + { + return $this->mongoDB->lastError(); + } + + /** @proxy */ + public function listCollections() + { + return $this->mongoDB->listCollections(); + } + + /** @proxy */ + public function prevError() + { + return $this->mongoDB->prevError(); + } + + /** @proxy */ + public function repair($preserveClonedFiles = false, $backupOriginalFiles = false) + { + return $this->mongoDB->repair($preserveClonedFiles, $backupOriginalFiles); + } + + /** @proxy */ + public function resetError() + { + return $this->mongoDB->resetError(); + } + + /** @proxy */ + public function selectCollection($name) + { + if ( ! isset($this->collections[$name])) { + $collection = $this->mongoDB->selectCollection($name); + $this->collections[$name] = new Collection( + $collection, $this, $this->eventManager, $this->loggerCallable, $this->cmd + ); + } + return $this->collections[$name]; + } + + /** @proxy */ + public function setProfilingLevel($level) + { + return $this->mongoDB->setProfilingLevel($level); + } + + /** @proxy */ + public function __toString() + { + return $this->mongoDB->__toString(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Events.php b/lib/Doctrine/MongoDB/Events.php new file mode 100644 index 00000000..80b1ca3c --- /dev/null +++ b/lib/Doctrine/MongoDB/Events.php @@ -0,0 +1,52 @@ +. + */ + +namespace Doctrine\MongoDB; + +/** + * Container for all Doctrine\MongoDB events. + * + * This class cannot be instantiated. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +final class Events +{ + private function __construct() {} + + const preBatchInsert = 'preBatchInsert'; + const postBatchInsert = 'postBatchInsert'; + + const preInsert = 'preInsert'; + const postInsert = 'postInsert'; + + const preUpdate = 'preUpdate'; + const postUpdate = 'postUpdate'; + + const preFind = 'preFind'; + const postFind = 'postFind'; + + const preFindOne = 'preFindOne'; + const postFindOne = 'postFindOne'; +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/GridFS.php b/lib/Doctrine/MongoDB/GridFS.php new file mode 100644 index 00000000..195c1bb7 --- /dev/null +++ b/lib/Doctrine/MongoDB/GridFS.php @@ -0,0 +1,114 @@ +mongoCollection->findOne($query, $fields); + if ($file) { + $document = $file->file; + $document['file'] = new GridFSFile($file); + $file = $document; + } + return $file; + } + + /** @override */ + protected function doUpdate($criteria, array $newObj, array $options = array()) + { + if (is_scalar($criteria)) { + $criteria = array('_id' => $criteria); + } + $file = isset($newObj[$this->cmd.'set']['file']) ? $newObj[$this->cmd.'set']['file'] : null; + unset($newObj[$this->cmd.'set']['file']); + if ($file === null) { + $file = isset($newObj['file']) ? $newObj['file'] : null; + unset($newObj['file']); + } + + // Has file to be persisted + if (isset($file) && $file->isDirty()) { + // It is impossible to update a file on the grid so we have to remove it and + // persist a new file with the same data + + // First do a find and remove query to remove the file metadata and chunks so + // we can restore the file below + $document = $this->findAndRemove($criteria, $options); + unset( + $document['filename'], + $document['length'], + $document['chunkSize'], + $document['uploadDate'], + $document['md5'], + $document['file'] + ); + + // Store the file + $this->storeFile($file, $document, $options); + } + + // Now send the original update bringing the file up to date + if ($newObj) { + if ( ! isset($newObj[$this->cmd.'set'])) { + unset($newObj['_id']); + $newObj = array($this->cmd.'set' => $newObj); + } + $this->mongoCollection->update($criteria, $newObj, $options); + } + return $newObj; + } + + /** @override */ + protected function doBatchInsert(array &$a, array $options = array()) + { + foreach ($a as $key => &$array) { + $this->doInsert($array, $options); + } + } + + /** @override */ + protected function doInsert(array &$a, array $options = array()) + { + // If file exists and is dirty then lets persist the file and store the file path or the bytes + if (isset($a['file'])) { + $file = $a['file']; // instanceof GridFSFile + unset($a['file']); + if ($file->isDirty()) { + $this->storeFile($file, $a, $options); + } else { + parent::doInsert($a, $options); + } + } else { + parent::doInsert($a, $options); + } + $a['file'] = $file; + return $a; + } + + /** @override */ + protected function doSave(array &$a, array $options = array()) + { + if (isset($a['_id'])) { + return $this->doUpdate(array('_id' => $a['_id']), $a, $options); + } else { + return $this->doInsert($a, $options); + } + } + + private function storeFile(GridFSFile $file, array &$document, array $options) + { + if ($file->hasUnpersistedFile()) { + $filename = $file->getFilename(); + $id = $this->mongoCollection->storeFile($filename, $document, $options); + } else { + $bytes = $file->getBytes(); + $id = $this->mongoCollection->storeBytes($bytes, $document, $options); + } + $document = array_merge(array('_id' => $id), $document); + $file->setMongoGridFSFile(new \MongoGridFSFile($this->mongoCollection, $document)); + return $file; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/GridFSFile.php b/lib/Doctrine/MongoDB/GridFSFile.php new file mode 100644 index 00000000..e322500b --- /dev/null +++ b/lib/Doctrine/MongoDB/GridFSFile.php @@ -0,0 +1,228 @@ +. + */ + +namespace Doctrine\MongoDB; + +/** + * File is a wrapper around the native PHP MongoGridFSFile class and allows you + * to use an instance of this class to persist new files as well as represent existing already + * persisted files using the MongoGridFS. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 1.0 + * @author Jonathan H. Wage + */ +class GridFSFile +{ + /** + * Stores \MongoGridFSFile instance. + * + * @var \MongoGridFSFile + */ + private $mongoGridFSFile; + + /** + * Path to a file that is/was pending persistence. + * + * @var string + */ + private $filename; + + /** + * Bytes that are/were pending persistence. + * + * @var string + */ + private $bytes; + + /** + * Whether or not the file is dirty and needs to be persisted. + * + * @var string + */ + private $isDirty = false; + + /** + * Constructs a new dirty file that needs persisting or wraps an existing PHP \MongoGridFSFile + * instance and does not need persistence unless changed and becomes dirty. + * + * @param string|\MongoGridFSFile $file + */ + public function __construct($file = null) + { + if ($file instanceof \MongoGridFSFile) { + $this->mongoGridFSFile = $file; + $this->isDirty = false; + } elseif (is_string($file)) { + $this->filename = $file; + $this->isDirty = true; + } + } + + /** + * Sets the persistent MongoGridFSFile instance + * + * @param \MongoGridFSFile $mongoGridFSFile + */ + public function setMongoGridFSFile(\MongoGridFSFile $mongoGridFSFile) + { + $this->mongoGridFSFile = $mongoGridFSFile; + $this->isDirty = false; + } + + /** + * Gets the persistent MongoGridFSFile instance + * + * @return \MongoGridFSFile + */ + public function getMongoGridFSFile() + { + return $this->mongoGridFSFile; + } + + /** + * Set a new filename to be persisted and marks the file as dirty. + * + * @param string $filename + */ + public function setFilename($filename) + { + $this->filename = $filename; + $this->isDirty = true; + } + + /** + * Gets the filename for this file. + * + * @return string $filename + */ + public function getFilename() + { + if ($this->isDirty && $this->filename) { + return $this->filename; + } elseif ($this->mongoGridFSFile instanceof \MongoGridFSFile && $filename = $this->mongoGridFSFile->getFilename()) { + return $filename; + } + return $this->filename; + } + + /** + * Sets new bytes to be persisted and marks the file as dirty. + * + * @param string $bytes + */ + public function setBytes($bytes) + { + $this->bytes = $bytes; + $this->isDirty = true; + } + + /** + * Gets the bytes for this file. + * + * @return string $bytes + */ + public function getBytes() + { + if ($this->isDirty && $this->bytes) { + return $this->bytes; + } + if ($this->filename) { + return file_get_contents($this->filename); + } + if ($this->mongoGridFSFile instanceof \MongoGridFSFile) { + return $this->mongoGridFSFile->getBytes(); + } + return null; + } + + /** + * Gets the size of this file. + * + * @return integer $size + */ + public function getSize() + { + if ($this->isDirty && $this->bytes) { + return strlen($this->bytes); + } + if ($this->isDirty && $this->filename) { + return filesize($this->filename); + } + if ($this->mongoGridFSFile instanceof \MongoGridFSFile) { + return $this->mongoGridFSFile->getSize(); + } + return 0; + } + + /** + * Writes this file to the given filename path. + * + * @param string $filename + * @return boolean TRUE if successful, and FALSE otherwise. + */ + public function write($filename) + { + if ($this->isDirty && $this->bytes) { + return file_put_contents($filename, $this->bytes); + } + if ($this->isDirty && $this->filename) { + return copy($this->filename, $filename); + } + if ($this->mongoGridFSFile instanceof \MongoGridFSFile) { + return $this->mongoGridFSFile->write($filename); + } + throw new \BadMethodCallException('Nothing to write(). File is not persisted yet and is not dirty.'); + } + + /** + * Check if the file is dirty or set isDirty by passing a boolean argument. + * + * @param boolean $bool + * @param boolean $isDirty + */ + public function isDirty($bool = null) + { + if ($bool !== null) { + $this->isDirty = $bool; + } + return $this->isDirty; + } + + /** + * Checks whether the file has some unpersisted bytes. + * + * @return boolean + */ + public function hasUnpersistedBytes() + { + return $this->isDirty && $this->bytes ? true : false; + } + + /** + * Checks whether the file has a unpersisted file. + * + * @return boolean + */ + public function hasUnpersistedFile() + { + return $this->isDirty && $this->filename ? true : false; + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Iterator.php b/lib/Doctrine/MongoDB/Iterator.php new file mode 100644 index 00000000..c8fd9431 --- /dev/null +++ b/lib/Doctrine/MongoDB/Iterator.php @@ -0,0 +1,11 @@ + + + + + + ./tests/Doctrine/ + + + \ No newline at end of file diff --git a/tests/Doctrine/MongoDB/Tests/BaseTest.php b/tests/Doctrine/MongoDB/Tests/BaseTest.php new file mode 100644 index 00000000..a38790f2 --- /dev/null +++ b/tests/Doctrine/MongoDB/Tests/BaseTest.php @@ -0,0 +1,21 @@ +listDBs(); + foreach ($dbs['databases'] as $db) { + $collections = $conn->selectDB($db['name'])->listCollections(); + foreach ($collections as $collection) { + $collection->drop(); + } + } + $conn->close(); + } +} \ No newline at end of file diff --git a/tests/Doctrine/MongoDB/Tests/FunctionalTest.php b/tests/Doctrine/MongoDB/Tests/FunctionalTest.php new file mode 100644 index 00000000..aa63fdaa --- /dev/null +++ b/tests/Doctrine/MongoDB/Tests/FunctionalTest.php @@ -0,0 +1,42 @@ +setLoggerCallable(function($msg) { + //print_r($msg); + }); + $conn = new Connection(null, array(), $config); + $db = $conn->selectDB('doctrine_mongodb'); + + /* + $coll = $db->selectCollection('users'); + + $document = array('test' => 'jwage'); + $coll->insert($document); + + $coll->update(array('_id' => $document['_id']), array('$set' => array('test' => 'jon'))); + + $cursor = $coll->find(); + print_r($cursor->getSingleResult()); + */ + + $files = $db->getGridFS('files'); + $file = array( + 'title' => 'test file', + 'testing' => 'ok', + 'file' => new GridFSFile(__DIR__.'/FunctionalTest.php') + ); + $files->insert($file, array('safe' => true)); +print_r($file); + $files->update(array('_id' => $file['_id']), array('$set' => array('title' => 'fuck', 'file' => new GridFSFile(__DIR__.'/BaseTest.php')))); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..0cebcc5a --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,14 @@ +register(); + +$classLoader = new ClassLoader('Doctrine\MongoDB', __DIR__ . '/../lib'); +$classLoader->register(); + +$classLoader = new ClassLoader('Doctrine\Common', __DIR__ . '/../lib/vendor/doctrine-common/lib'); +$classLoader->register(); \ No newline at end of file