├── tests ├── Shanty │ ├── Mongo │ │ ├── _files │ │ │ ├── My │ │ │ │ └── ShantyMongo │ │ │ │ │ ├── InvalidDocument.php │ │ │ │ │ ├── Abstract.php │ │ │ │ │ ├── Users.php │ │ │ │ │ ├── ArtStudent.php │ │ │ │ │ ├── Teacher.php │ │ │ │ │ ├── Student.php │ │ │ │ │ ├── Country.php │ │ │ │ │ ├── Simple.php │ │ │ │ │ ├── Name.php │ │ │ │ │ ├── SimpleSubDocWithExport.php │ │ │ │ │ ├── SimpleWithExport.php │ │ │ │ │ ├── Article.php │ │ │ │ │ └── User.php │ │ │ └── countries.json │ │ ├── TestSetup.php │ │ ├── Validate │ │ │ ├── ArrayTest.php │ │ │ ├── StubTrueTest.php │ │ │ └── ClassTest.php │ │ ├── Iterator │ │ │ ├── DefaultTest.php │ │ │ └── CursorTest.php │ │ ├── ConnectionTest.php │ │ ├── Connection │ │ │ ├── StackTest.php │ │ │ └── GroupTest.php │ │ ├── DocumentSetTest.php │ │ └── CollectionTest.php │ ├── Paginator │ │ ├── TestSetup.php │ │ └── Adapter │ │ │ └── MongoTest.php │ ├── TestSetup.php │ └── MongoTest.php └── bootstrap.php ├── .gitignore ├── library └── Shanty │ ├── Mongo │ ├── Validate │ │ ├── StubTrue.php │ │ ├── Array.php │ │ └── Class.php │ ├── Exception.php │ ├── Iterator │ │ ├── Default.php │ │ └── Cursor.php │ ├── Connection.php │ ├── Connection │ │ ├── Stack.php │ │ └── Group.php │ ├── DocumentSet.php │ └── Collection.php │ ├── Paginator │ └── Adapter │ │ └── Mongo.php │ └── Mongo.php ├── TODO ├── .travis.yml ├── Changelog ├── phpunit.xml.dist ├── composer.json ├── LICENSE └── README.markdown /tests/Shanty/Mongo/_files/My/ShantyMongo/InvalidDocument.php: -------------------------------------------------------------------------------- 1 | 'Required' 9 | ); 10 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/Student.php: -------------------------------------------------------------------------------- 1 | 'Required' 9 | ); 10 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/Country.php: -------------------------------------------------------------------------------- 1 | > ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 10 | # Install dev requirements 11 | - composer install --dev --prefer-source -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/Name.php: -------------------------------------------------------------------------------- 1 | 'Required', 9 | 'last' => 'Required' 10 | ); 11 | 12 | public function full() 13 | { 14 | return $this->first.' '.$this->last; 15 | } 16 | 17 | public function __toString() 18 | { 19 | return $this->full(); 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Validate/ArrayTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($validator->isValid(array())); 12 | $this->assertFalse($validator->isValid(42)); 13 | } 14 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Validate/Array.php: -------------------------------------------------------------------------------- 1 | "Value is not an Array" 11 | ); 12 | 13 | public function isValid($value) 14 | { 15 | if (!is_array($value)) { 16 | $this->_error(self::NOT_ARRAY); 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Validate/StubTrueTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($validator->isValid($this->getMock('My_ShantyMongo_User'))); 12 | $this->assertTrue($validator->isValid(42)); 13 | } 14 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Validate/ClassTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($validator->isValid($this->getMock('My_ShantyMongo_User'))); 12 | $this->assertFalse($validator->isValid(42)); 13 | } 14 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/SimpleSubDocWithExport.php: -------------------------------------------------------------------------------- 1 | 'Ignore', 9 | 'subDoc' => array('Document:My_ShantyMongo_SimpleSubDocWithExport') 10 | ); 11 | 12 | public function export($skipRequired = false) { 13 | $data = parent::export($skipRequired); 14 | $data['myIgnoredProperty'] = 'sub document data'; 15 | $data['myUnignoredProperty'] = 'sub document data'; 16 | return $data; 17 | } 18 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/SimpleWithExport.php: -------------------------------------------------------------------------------- 1 | 'Ignore', 9 | 'subDoc' => array('Document:My_ShantyMongo_SimpleSubDocWithExport') 10 | ); 11 | 12 | protected static $_db = TESTS_SHANTY_MONGO_DB; 13 | protected static $_collection = 'simple'; 14 | 15 | public function export($skipRequired = false) { 16 | $data = parent::export($skipRequired); 17 | $data['myIgnoredProperty'] = 'some data'; 18 | $data['myUnignoredProperty'] = 'some data'; 19 | return $data; 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/Article.php: -------------------------------------------------------------------------------- 1 | array('Required', 'Filter:StringTrim'), 12 | 'author' => array('Document:My_ShantyMongo_User', 'AsReference'), 13 | 'contributors' => 'DocumentSet:My_ShantyMongo_Users', 14 | 'contributors.$' => array('Document:My_ShantyMongo_User', 'AsReference'), 15 | 'relatedArticles' => 'DocumentSet', 16 | 'relatedArticles.$' => 'Document:My_ShantyMongo_Article', 17 | 'tags' => 'Array' 18 | ); 19 | } -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | Changes for v0.4 2 | - Support for connection options timeout, persist and replicaSet 3 | - Connection option 'replica_pair' has been renamed to 'hosts' 4 | - Added Shanty_Mongo_Collection::distinct() 5 | - Shanty_Mongo_Collection::remove now accepts options 6 | - Shanty_Mongo_Document::delete() is now safe by default 7 | - Added pre and post hooks for delete 8 | - Added batch insert 9 | - Added Shanty Paginator 10 | Changes for v0.3.2 11 | - Saves now safe by default 12 | Changes for v0.3 13 | - Protected static property Shanty_Mongo_Document::$_dbName has been renamed to $_db 14 | - Protected static property Shanty_Mongo_Document::$_collectionName has been renamed to $_collection 15 | - Protected property Shanty_Mongo_Document::$_requirements is now a static property 16 | - Support for inheritance 17 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | ./tests 8 | 9 | 10 | ./tests/Shanty/Paginator 11 | 12 | 13 | ./tests/Shanty/Mongo 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | library 23 | 24 | 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coen-hyde/shanty-mongo", 3 | "description": "Shanty Mongo is a mongodb library for the Zend Framework.", 4 | "keywords": ["zend", "mongodb", "orm", "nosql"], 5 | "homepage": "https://github.com/coen-hyde/Shanty-Mongo", 6 | "type": "library", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "Coen Hyde", 11 | "homepage": "http://coenhyde.com" 12 | }, 13 | { 14 | "name": "Tom Holder", 15 | "homepage": "http://www.simpleweb.co.uk" 16 | }, 17 | { 18 | "name": "Johnson Page", 19 | "homepage": "http://johnsonpage.org" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=5.3" 24 | }, 25 | "require-dev": { 26 | "digital-canvas/zend-framework": "1.12.0", 27 | "phpunit/phpunit": "~3.7.0" 28 | }, 29 | "autoload": { 30 | "psr-0": { "Shanty": "library/" } 31 | }, 32 | "include-path": [ "library/" ] 33 | } 34 | -------------------------------------------------------------------------------- /library/Shanty/Mongo/Validate/Class.php: -------------------------------------------------------------------------------- 1 | "'%value%' is not a %class%" 14 | ); 15 | 16 | /** 17 | * @var array 18 | */ 19 | protected $_messageVariables = array( 20 | 'class' => '_class' 21 | ); 22 | 23 | protected $_class = null; 24 | 25 | public function __construct($class) 26 | { 27 | $this->setClass($class); 28 | } 29 | 30 | public function setClass($class) 31 | { 32 | $this->_class = $class; 33 | } 34 | 35 | public function getClass() 36 | { 37 | return $this->_class; 38 | } 39 | 40 | public function isValid($value) 41 | { 42 | $this->_setValue($value); 43 | $class = $this->getClass(); 44 | 45 | if (!($value instanceof $class)) { 46 | $this->_error(self::CLASS_NOT_VALID); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | } -------------------------------------------------------------------------------- /tests/Shanty/Paginator/Adapter/MongoTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(239, $countries->count()); 13 | 14 | $paginator = new Zend_Paginator(new Shanty_Paginator_Adapter_Mongo($countries)); 15 | $paginator->setItemCountPerPage(10); 16 | $paginator->setCurrentPageNumber(3); 17 | 18 | $this->assertEquals(24, $paginator->count()); // Count pages 19 | $this->assertEquals(239, $paginator->getTotalItemCount()); // count total items 20 | $this->assertEquals(10, $paginator->getCurrentItemCount()); // count items on this page 21 | 22 | $paginator->getCurrentItems()->rewind(); 23 | $firstItem = $paginator->getCurrentItems()->current(); 24 | 25 | $this->assertEquals($firstItem->code, 'BB'); 26 | $this->assertEquals($firstItem->name, 'Barbados'); 27 | } 28 | } -------------------------------------------------------------------------------- /library/Shanty/Paginator/Adapter/Mongo.php: -------------------------------------------------------------------------------- 1 | _cursor = $cursor; 32 | } 33 | 34 | /** 35 | * Returns an cursor limited to items for a page. 36 | * 37 | * @param integer $offset Page offset 38 | * @param integer $itemCountPerPage Number of items per page 39 | * @return Shanty_Mongo_Iterator_Cursor 40 | */ 41 | public function getItems($offset, $itemCountPerPage) 42 | { 43 | $cursor = $this->_cursor->skip($offset)->limit($itemCountPerPage); 44 | return $cursor; 45 | } 46 | 47 | /** 48 | * Returns the total number of rows in the cursor. 49 | * 50 | * @return integer 51 | */ 52 | public function count() 53 | { 54 | return $this->_cursor->count(); 55 | } 56 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Shanty Tech Pty Ltd 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL SHANTY TECH PTY LTD BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/My/ShantyMongo/User.php: -------------------------------------------------------------------------------- 1 | array('Document:My_ShantyMongo_Name', 'Required'), 12 | 'email' => array('Required', 'Validator:EmailAddress'), 13 | 'addresses' => 'DocumentSet', 14 | 'addresses.$.street' => 'Required', 15 | 'addresses.$.state' => 'Required', 16 | 'addresses.$.suburb' => 'Required', 17 | 'addresses.$.postcode' => 'Required', 18 | 'friends' => 'DocumentSet:My_ShantyMongo_Users', 19 | 'friends.$' => array('Document:My_ShantyMongo_User', 'AsReference'), 20 | 'sex' => array('Required', 'Validator:InArray' => array('F', 'M')), 21 | 'partner' => array('Document:My_ShantyMongo_User', 'AsReference') 22 | ); 23 | 24 | public $_hookCounter = array( 25 | 'init' => 0, 26 | 'preInsert' => 0, 27 | 'postInsert' => 0, 28 | 'preUpdate' => 0, 29 | 'postUpdate' => 0, 30 | 'preSave' => 0, 31 | 'postSave' => 0 32 | ); 33 | 34 | protected function init() 35 | { 36 | $this->_hookCounter['init'] += 1; 37 | } 38 | 39 | protected function preInsert() 40 | { 41 | $this->_hookCounter['preInsert'] += 1; 42 | } 43 | 44 | protected function postInsert() 45 | { 46 | $this->_hookCounter['postInsert'] += 1; 47 | } 48 | 49 | protected function preUpdate() 50 | { 51 | $this->_hookCounter['preUpdate'] += 1; 52 | } 53 | 54 | protected function postUpdate() 55 | { 56 | $this->_hookCounter['postUpdate'] += 1; 57 | } 58 | 59 | protected function preSave() 60 | { 61 | $this->_hookCounter['preSave'] += 1; 62 | } 63 | 64 | protected function postSave() 65 | { 66 | $this->_hookCounter['postSave'] += 1; 67 | } 68 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Iterator/DefaultTest.php: -------------------------------------------------------------------------------- 1 | _document = My_ShantyMongo_User::find('4c04516a1f5f5e21361e3ab0'); 15 | $this->_iterator = $this->_document->getIterator(); 16 | } 17 | 18 | public function testGetDocument() 19 | { 20 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_iterator->getDocument()); 21 | $this->assertTrue($this->_iterator->getDocument() instanceof My_ShantyMongo_User); 22 | $this->assertEquals('My_ShantyMongo_Teacher', get_class($this->_iterator->getDocument())); 23 | } 24 | 25 | public function testGetDocumentProperties() 26 | { 27 | $this->assertEquals($this->_document->getPropertyKeys(), $this->_iterator->getDocumentProperties()); 28 | } 29 | 30 | public function testIteration() 31 | { 32 | $documentProperties = $this->_document->getPropertyKeys(); 33 | $this->assertEquals($documentProperties[0], $this->_iterator->key()); 34 | 35 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_iterator->current()); 36 | $this->assertEquals('MongoId', get_class($this->_iterator->current())); 37 | $this->assertFalse($this->_iterator->hasChildren()); 38 | 39 | $this->_iterator->next(); 40 | $this->_iterator->next(); 41 | $this->assertEquals($documentProperties[2], $this->_iterator->key()); 42 | 43 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_iterator->current()); 44 | $this->assertEquals('My_ShantyMongo_Name', get_class($this->_iterator->current())); 45 | 46 | $this->_iterator->seek('addresses'); 47 | $this->assertEquals('addresses', $this->_iterator->key()); 48 | 49 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_iterator->current()); 50 | $this->assertEquals('Shanty_Mongo_DocumentSet', get_class($this->_iterator->current())); 51 | $this->assertTrue($this->_iterator->hasChildren()); 52 | 53 | $addresses = $this->_iterator->getChildren(); 54 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $addresses); 55 | $this->assertEquals('Shanty_Mongo_Iterator_Default', get_class($addresses)); 56 | $this->assertEquals(2, count($addresses)); 57 | 58 | $this->_iterator->rewind(); 59 | $this->assertEquals($documentProperties[0], $this->_iterator->key()); 60 | } 61 | 62 | /** 63 | * @expectedException OutOfBoundsException 64 | */ 65 | public function testSeekException() 66 | { 67 | $this->_iterator->seek('key does not exist'); 68 | } 69 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Iterator/Default.php: -------------------------------------------------------------------------------- 1 | _document = $document; 21 | $this->_properties = $document->getPropertyKeys(); 22 | $this->_position = current($this->_properties); 23 | 24 | reset($this->_properties); 25 | } 26 | 27 | /** 28 | * Get the document 29 | * 30 | * @return Shanty_Mongo_Document 31 | */ 32 | public function getDocument() 33 | { 34 | return $this->_document; 35 | } 36 | 37 | /** 38 | * Get the properties 39 | * 40 | * @return array 41 | */ 42 | public function getDocumentProperties() 43 | { 44 | return $this->_properties; 45 | } 46 | 47 | /** 48 | * Seek to a position 49 | * 50 | * @param unknown_type $position 51 | */ 52 | public function seek($position) 53 | { 54 | $this->_position = $position; 55 | 56 | if (!$this->valid()) { 57 | throw new OutOfBoundsException("invalid seek position ($position)"); 58 | } 59 | } 60 | 61 | /** 62 | * Get the current value 63 | * 64 | * @return mixed 65 | */ 66 | public function current() 67 | { 68 | return $this->getDocument()->getProperty($this->key()); 69 | } 70 | 71 | /** 72 | * Get the current key 73 | * 74 | * @return string 75 | */ 76 | public function key() 77 | { 78 | return $this->_position; 79 | } 80 | 81 | /** 82 | * Move next 83 | */ 84 | public function next() 85 | { 86 | next($this->_properties); 87 | $this->_position = current($this->_properties); 88 | $this->_counter = $this->_counter + 1; 89 | } 90 | 91 | /** 92 | * Rewind the iterator 93 | */ 94 | public function rewind() 95 | { 96 | reset($this->_properties); 97 | $this->_position = current($this->_properties); 98 | } 99 | 100 | /** 101 | * Is the current position valid 102 | * 103 | * @return boolean 104 | */ 105 | public function valid() 106 | { 107 | return in_array($this->key(), $this->_properties, true); 108 | } 109 | 110 | /** 111 | * Count all properties 112 | * 113 | * @return int 114 | */ 115 | public function count() 116 | { 117 | return count($this->getDocumentProperties()); 118 | } 119 | 120 | /** 121 | * Determine if the current node has an children 122 | * 123 | * @return boolean 124 | */ 125 | public function hasChildren() 126 | { 127 | return ($this->current() instanceof Shanty_Mongo_Document); 128 | } 129 | 130 | /* 131 | * Get children 132 | * 133 | * @return RecursiveIterator 134 | */ 135 | public function getChildren() 136 | { 137 | return $this->current()->getIterator(); 138 | } 139 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/ConnectionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($options, Shanty_Mongo_Connection::getAvailableOptions()); 21 | } 22 | 23 | public function testParseHostString() 24 | { 25 | $hostInfo = array( 26 | 'host' => 'localhost' 27 | ); 28 | 29 | $this->assertEquals($hostInfo, Shanty_Mongo_Connection::parseHostString('localhost')); 30 | 31 | $hostInfo = array( 32 | 'host' => 'localhost', 33 | 'port' => 27017 34 | ); 35 | 36 | $this->assertEquals($hostInfo, Shanty_Mongo_Connection::parseHostString('localhost:27017')); 37 | 38 | $hostInfo = array( 39 | 'host' => 'localhost', 40 | 'username' => 'coen' 41 | ); 42 | 43 | $this->assertEquals($hostInfo, Shanty_Mongo_Connection::parseHostString('coen@localhost')); 44 | 45 | $hostInfo = array( 46 | 'username' => 'coen', 47 | 'password' => 'pass', 48 | 'host' => '127.0.0.1', 49 | 'port' => '27017' 50 | ); 51 | 52 | $this->assertEquals($hostInfo, Shanty_Mongo_Connection::parseHostString('coen:pass@127.0.0.1:27017')); 53 | } 54 | 55 | /** 56 | * @depends testParseHostString 57 | */ 58 | public function testParseConnectionString() 59 | { 60 | // Single host 61 | $connectionInfo = array( 62 | 'connectionString' => 'mongodb://127.0.0.1:27017/shanty-mongo', 63 | 'database' => 'shanty-mongo', 64 | 'hosts' => array( 65 | array ( 66 | 'host' => '127.0.0.1', 67 | 'port' => 27017 68 | ) 69 | ) 70 | ); 71 | $this->assertEquals($connectionInfo, Shanty_Mongo_Connection::parseConnectionString('mongodb://127.0.0.1:27017/shanty-mongo')); 72 | 73 | // Multiple hosts 74 | $connectionInfo = array( 75 | 'connectionString' => 'mongodb://127.0.0.1:27017,coen:pass@localhost:27018/shanty-mongo', 76 | 'database' => 'shanty-mongo', 77 | 'hosts' => array( 78 | array ( 79 | 'host' => '127.0.0.1', 80 | 'port' => 27017 81 | ), 82 | array ( 83 | 'username' => 'coen', 84 | 'password' => 'pass', 85 | 'host' => 'localhost', 86 | 'port' => 27018 87 | ) 88 | ) 89 | ); 90 | $this->assertEquals($connectionInfo, Shanty_Mongo_Connection::parseConnectionString('mongodb://127.0.0.1:27017,coen:pass@localhost:27018/shanty-mongo')); 91 | } 92 | 93 | /** 94 | * @depends testParseConnectionString 95 | */ 96 | public function testGetDatabase() 97 | { 98 | $connection = new Shanty_Mongo_Connection('mongodb://127.0.0.1:27017/shanty-mongo'); 99 | $this->assertEquals('shanty-mongo', $connection->getDatabase()); 100 | } 101 | 102 | /** 103 | * @depends testParseConnectionString 104 | */ 105 | public function testGetHosts() 106 | { 107 | $connection = new Shanty_Mongo_Connection('mongodb://127.0.0.1:27017/shanty-mongo'); 108 | $hosts = array( 109 | array( 110 | 'host' => '127.0.0.1', 111 | 'port' => 27017 112 | ) 113 | ); 114 | 115 | $this->assertEquals($hosts, $connection->getHosts()); 116 | } 117 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Iterator/Cursor.php: -------------------------------------------------------------------------------- 1 | _cursor = $cursor; 18 | $this->_config = $config; 19 | } 20 | 21 | /** 22 | * Get the inter iterator 23 | * 24 | * @return MongoCursor 25 | */ 26 | public function getInnerIterator() 27 | { 28 | return $this->_cursor; 29 | } 30 | 31 | /** 32 | * Get the document class 33 | * 34 | * @return string 35 | */ 36 | public function getDocumentClass() 37 | { 38 | return $this->_config['documentClass']; 39 | } 40 | 41 | /** 42 | * Get the document set class 43 | * 44 | * @return string 45 | */ 46 | public function getDocumentSetClass() 47 | { 48 | return $this->_config['documentSetClass']; 49 | } 50 | 51 | /** 52 | * Export all data 53 | * 54 | * @return array 55 | */ 56 | public function export() 57 | { 58 | $this->rewind(); 59 | return iterator_to_array($this->getInnerIterator()); 60 | } 61 | 62 | /** 63 | * Construct a document set from this cursor 64 | * 65 | * @return Shanty_Mongo_DocumentSet 66 | */ 67 | public function makeDocumentSet() 68 | { 69 | $config = array(); 70 | $config['new'] = false; 71 | $config['hasId'] = false; 72 | $config['connectionGroup'] = $this->_config['connectionGroup']; 73 | $config['db'] = $this->_config['db']; 74 | $config['collection'] = $this->_config['collection']; 75 | $config['requirementModifiers'] = array( 76 | Shanty_Mongo_DocumentSet::DYNAMIC_INDEX => array("Document:".$this->getDocumentClass()) 77 | ); 78 | 79 | $documentSetClass = $this->getDocumentSetClass(); 80 | return new $documentSetClass($this->export(), $config); 81 | } 82 | 83 | /** 84 | * Get the current value 85 | * 86 | * @return mixed 87 | */ 88 | public function current() 89 | { 90 | $data = $this->getInnerIterator()->current(); 91 | if ($data === null) { 92 | return null; 93 | } 94 | 95 | $config = array(); 96 | $config['new'] = false; 97 | $config['hasKey'] = true; 98 | $config['connectionGroup'] = $this->_config['connectionGroup']; 99 | $config['db'] = $this->_config['db']; 100 | $config['collection'] = $this->_config['collection']; 101 | 102 | $documentClass = $this->getDocumentClass(); 103 | if (!empty($data['_type'][0])) { 104 | $documentClass = $data['_type'][0]; 105 | } 106 | 107 | return new $documentClass($data, $config); 108 | } 109 | 110 | public function getNext() 111 | { 112 | $this->next(); 113 | return $this->current(); 114 | } 115 | 116 | public function key() 117 | { 118 | return $this->getInnerIterator()->key(); 119 | } 120 | 121 | public function next() 122 | { 123 | return $this->getInnerIterator()->next(); 124 | } 125 | 126 | public function rewind() 127 | { 128 | return $this->getInnerIterator()->rewind(); 129 | } 130 | 131 | public function valid() 132 | { 133 | return $this->getInnerIterator()->valid(); 134 | } 135 | 136 | public function count($all = false) 137 | { 138 | return $this->getInnerIterator()->count($all); 139 | } 140 | 141 | public function info() 142 | { 143 | return $this->getInnerIterator()->info(); 144 | } 145 | 146 | public function __call($method, $arguments) 147 | { 148 | // Forward the call to the MongoCursor 149 | $res = call_user_func_array(array($this->getInnerIterator(), $method), $arguments); 150 | 151 | // Allow chaining 152 | if ($res instanceof MongoCursor) { 153 | return $this; 154 | } 155 | 156 | return $res; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Iterator/CursorTest.php: -------------------------------------------------------------------------------- 1 | _cursor = new Shanty_Mongo_Iterator_Cursor($this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('user')->find(), $config); 21 | } 22 | 23 | public function testGetInnerIterator() 24 | { 25 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_cursor->getInnerIterator()); 26 | $this->assertEquals('MongoCursor', get_class($this->_cursor->getInnerIterator())); 27 | } 28 | 29 | public function testGetDocumentClass() 30 | { 31 | $this->assertEquals('My_ShantyMongo_User', $this->_cursor->getDocumentClass()); 32 | } 33 | 34 | public function testGetDocumentSetClass() 35 | { 36 | $this->assertEquals('My_ShantyMongo_Users', $this->_cursor->getDocumentSetClass()); 37 | } 38 | 39 | public function testExport() 40 | { 41 | $exportData = array( 42 | '4c04516a1f5f5e21361e3ab0' => $this->_users['bob'], 43 | '4c04516f1f5f5e21361e3ab1' => $this->_users['cherry'], 44 | '4c0451791f5f5e21361e3ab2' => $this->_users['roger'], 45 | ); 46 | 47 | $this->assertEquals($exportData, $this->_cursor->export()); 48 | } 49 | 50 | public function testMakeDocumentSet() 51 | { 52 | $documentSet = $this->_cursor->makeDocumentSet(); 53 | 54 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $documentSet); 55 | $this->assertEquals('My_ShantyMongo_Users', get_class($documentSet)); 56 | } 57 | 58 | public function testIterate() 59 | { 60 | $names = array( 61 | '4c04516a1f5f5e21361e3ab0' => 'Bob Jones', 62 | '4c04516f1f5f5e21361e3ab1' => 'Cherry Jones', 63 | '4c0451791f5f5e21361e3ab2' => 'Roger Smith' 64 | ); 65 | 66 | $namesCompare = array(); 67 | 68 | foreach ($this->_cursor as $userId => $user) { 69 | switch ($userId) { 70 | case '4c04516a1f5f5e21361e3ab0': 71 | $this->assertEquals('My_ShantyMongo_Teacher', get_class($user)); 72 | break; 73 | 74 | case '4c04516f1f5f5e21361e3ab1': 75 | $this->assertEquals('My_ShantyMongo_Student', get_class($user)); 76 | break; 77 | 78 | case '4c0451791f5f5e21361e3ab2': 79 | $this->assertEquals('My_ShantyMongo_ArtStudent', get_class($user)); 80 | break; 81 | } 82 | 83 | $namesCompare[$userId] = $user->name->full(); 84 | } 85 | 86 | $this->assertEquals($names, $namesCompare); 87 | } 88 | 89 | public function testCount() 90 | { 91 | $this->assertEquals(3, $this->_cursor->count()); 92 | } 93 | 94 | public function testGetNext() 95 | { 96 | $this->assertEquals('Bob Jones', $this->_cursor->getNext()->name->full()); 97 | $this->assertEquals('Cherry Jones', $this->_cursor->getNext()->name->full()); 98 | $this->assertEquals('Roger Smith', $this->_cursor->getNext()->name->full()); 99 | $this->assertEquals(null, $this->_cursor->getNext()); 100 | } 101 | 102 | public function testInfo() 103 | { 104 | $info = $this->_cursor->info(); 105 | $this->assertTrue(is_array($this->_cursor->info())); 106 | } 107 | 108 | /** 109 | * @depends testInfo 110 | */ 111 | public function testMagicCall() 112 | { 113 | // Magic call returning Shanty_Mongo_Iterator_Cursor for chaining 114 | $cursor = $this->_cursor->limit(10)->sort(array('name.first')); 115 | 116 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $cursor); 117 | $this->assertEquals('Shanty_Mongo_Iterator_Cursor', get_class($cursor)); 118 | 119 | $info = $this->_cursor->info(); 120 | $this->assertEquals(10, $info['limit']); 121 | $this->assertEquals(array('name.first'), $info['query']['$orderby']); 122 | 123 | // Magic call returning boolean 124 | $this->assertTrue($cursor->hasNext()); 125 | } 126 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Connection.php: -------------------------------------------------------------------------------- 1 | _connectionInfo = array_merge($options, $connectionInfo); 34 | 35 | return parent::__construct($connectionString, $options); 36 | } 37 | 38 | /** 39 | * Get some info about this connection 40 | * 41 | * @return array 42 | */ 43 | public function getConnectionInfo() 44 | { 45 | return $this->_connectionInfo; 46 | } 47 | 48 | /** 49 | * Get the actual connection string used for this connection. This differs from __toString in 50 | * that __toString returns a string representation of the connection, not the connection string used 51 | * 52 | * @return array 53 | */ 54 | public function getActualConnectionString() 55 | { 56 | return $this->_connectionInfo['connectionString']; 57 | } 58 | 59 | /** 60 | * Get the database this connection is connection to 61 | * 62 | * @return string 63 | */ 64 | public function getDatabase() 65 | { 66 | if (!isset($this->_connectionInfo['database'])) return null; 67 | 68 | return $this->_connectionInfo['database']; 69 | } 70 | 71 | /** 72 | * Get a list of the hosts this connection is connection to 73 | * 74 | * @return array 75 | */ 76 | public function getHosts() 77 | { 78 | return $this->_connectionInfo['hosts']; 79 | } 80 | 81 | /** 82 | * Parse the connection string 83 | * 84 | * @param $connectionString 85 | * @return array 86 | */ 87 | public static function parseConnectionString($connectionString) 88 | { 89 | $connectionInfo = array( 90 | 'connectionString' => $connectionString 91 | ); 92 | 93 | // Remove mongodb protocol string 94 | if (substr($connectionString, 0, 10) == 'mongodb://') { 95 | $connectionString = substr($connectionString, 10); 96 | } 97 | 98 | // Is there a database name 99 | if ($pos = strrpos($connectionString, '/')) { 100 | $connectionInfo['database'] = substr($connectionString, $pos+1); 101 | $connectionString = substr($connectionString, 0, $pos); 102 | } 103 | 104 | // Fetch Hosts 105 | if (!empty($connectionString)) { 106 | $hostStrings = explode(',', $connectionString); 107 | $connectionInfo['hosts'] = array(); 108 | foreach ($hostStrings as $hostString) { 109 | $connectionInfo['hosts'][] = self::parseHostString($hostString); 110 | } 111 | } 112 | 113 | return $connectionInfo; 114 | } 115 | 116 | /** 117 | * Parse a host string 118 | * 119 | * @param $hostString 120 | * @return array 121 | */ 122 | public static function parseHostString($hostString) 123 | { 124 | $hostInfo = array(); 125 | 126 | // Are we authenticating with a username and password? 127 | if ($pos = strpos($hostString, '@')) { 128 | $authString = substr($hostString, 0, $pos); 129 | 130 | $data = explode(':', $authString); 131 | $hostInfo['username'] = $data[0]; 132 | 133 | if (count($data) > 1) { 134 | $hostInfo['password'] = $data[1]; 135 | } 136 | 137 | $hostString = substr($hostString, $pos+1); 138 | } 139 | 140 | // Grab host and port 141 | $data = explode(':', $hostString); 142 | $hostInfo['host'] = $data[0]; 143 | 144 | if (count($data) > 1) { 145 | $hostInfo['port'] = $data[1]; 146 | } 147 | 148 | return $hostInfo; 149 | } 150 | 151 | /** 152 | * Get available options 153 | * 154 | * @return array 155 | */ 156 | static public function getAvailableOptions() 157 | { 158 | return static::$_availableOptions; 159 | } 160 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Connection/Stack.php: -------------------------------------------------------------------------------- 1 | true 17 | ); 18 | protected $_cacheConnectionSelection = true; 19 | protected $_cachedConnection = null; 20 | 21 | /** 22 | * Get an option 23 | * 24 | * @param string $option 25 | */ 26 | public function getOption($option) 27 | { 28 | if (!array_key_exists($option, $this->_options)) { 29 | return null; 30 | } 31 | 32 | return $this->_options[$option]; 33 | } 34 | 35 | /** 36 | * Set an option 37 | * 38 | * @param string $option 39 | * @param mixed $value 40 | */ 41 | public function setOption($option, $value) 42 | { 43 | $this->_options[$option] = $value; 44 | } 45 | 46 | /** 47 | * Set Options 48 | * 49 | * @param array $options 50 | */ 51 | public function setOptions(array $options) 52 | { 53 | $this->_options = array_merge($this->_options, $options); 54 | } 55 | 56 | /** 57 | * Add node to connection stack 58 | * 59 | * @param Shanty_Mongo_Connection $connection 60 | * @param int $weight 61 | */ 62 | public function addNode(Shanty_Mongo_Connection $connection, $weight = 1) 63 | { 64 | $this->_nodes[] = $connection; 65 | $this->_weights[] = (int) $weight; 66 | } 67 | 68 | /** 69 | * Select a node from the connection stack. 70 | * 71 | * @return Shanty_Mongo_Connection 72 | */ 73 | public function selectNode() 74 | { 75 | if (count($this) == 0) { 76 | // no nodes to select from 77 | return null; 78 | } 79 | 80 | // Return the cached connection if available 81 | if ($this->cacheConnectionSelection() && $this->hasCachedConnection()) { 82 | return $this->getCachedConnection(); 83 | } 84 | 85 | // Select a new connection 86 | $r = mt_rand(1,array_sum($this->_weights)); 87 | $offset = 0; 88 | foreach ($this->_weights as $k => $weight) { 89 | $offset += $weight; 90 | if ($r <= $offset) { 91 | $connection = $this->_nodes[$k]; 92 | break; 93 | } 94 | } 95 | 96 | // Cache the connection for later use 97 | if ($this->cacheConnectionSelection()) { 98 | $this->_cachedConnection = $connection; 99 | } 100 | 101 | return $connection; 102 | } 103 | 104 | /** 105 | * Determine if this connection stack has a cached connection 106 | * 107 | * @return boolean 108 | */ 109 | public function hasCachedConnection() 110 | { 111 | return !is_null($this->_cachedConnection); 112 | } 113 | 114 | /** 115 | * Get the cached connection 116 | * 117 | * @return Shanty_Mongo_Connection 118 | */ 119 | public function getCachedConnection() 120 | { 121 | return $this->_cachedConnection; 122 | } 123 | 124 | /** 125 | * Get or set the flag to determine if the first connection selection should be cached 126 | * 127 | * @param boolean $value 128 | */ 129 | public function cacheConnectionSelection($value = null) 130 | { 131 | if (!is_null($value)) { 132 | $this->_options['cacheConnectionSelection'] = (boolean) $value; 133 | } 134 | 135 | return $this->_options['cacheConnectionSelection']; 136 | } 137 | 138 | /** 139 | * Seek to a particular connection 140 | * 141 | * @param $position 142 | */ 143 | public function seek($position) 144 | { 145 | if (!is_numeric($position)) { 146 | require_once 'Shanty/Mongo/Exception.php'; 147 | throw new Shanty_Mongo_Exception("Position must be numeric"); 148 | } 149 | 150 | $this->_position = $position; 151 | 152 | if (!$this->valid()) { 153 | throw new OutOfBoundsException("invalid seek position ($position)"); 154 | } 155 | } 156 | 157 | /** 158 | * Get the current connection 159 | * 160 | * @return Shanty_Mongo_Connection 161 | */ 162 | public function current() 163 | { 164 | return $this->_nodes[$this->_position]; 165 | } 166 | 167 | /** 168 | * Get teh current key 169 | * 170 | * @return int 171 | */ 172 | public function key() 173 | { 174 | return $this->_position; 175 | } 176 | 177 | /** 178 | * Move the pointer to the next connection 179 | */ 180 | public function next() 181 | { 182 | $this->_position +=1; 183 | } 184 | 185 | /** 186 | * Rewind the pointer to the begining of the stack 187 | */ 188 | public function rewind() 189 | { 190 | $this->_position = 0; 191 | } 192 | 193 | /** 194 | * Is the location of the current pointer valid 195 | */ 196 | public function valid() 197 | { 198 | return $this->offsetExists($this->_position); 199 | } 200 | 201 | /** 202 | * Count all the connections 203 | */ 204 | public function count() 205 | { 206 | return count($this->_nodes); 207 | } 208 | 209 | /** 210 | * Test if an offset exists 211 | * 212 | * @param int $offset 213 | */ 214 | public function offsetExists($offset) 215 | { 216 | return array_key_exists($offset, $this->_nodes); 217 | } 218 | 219 | /** 220 | * Get an offset 221 | * 222 | * @param int $offset 223 | */ 224 | public function offsetGet($offset) 225 | { 226 | if (!$this->offsetExists($offset)) return null; 227 | 228 | return $this->_nodes[$offset]; 229 | } 230 | 231 | /** 232 | * Set an offset 233 | * 234 | * @param Shanty_Mongo_Connection $offset 235 | * @param $connection 236 | */ 237 | public function offsetSet($offset, $connection) 238 | { 239 | if (!is_numeric($offset)) { 240 | require_once 'Shanty/Mongo/Exception.php'; 241 | throw new Shanty_Mongo_Exception("Offset must be numeric"); 242 | } 243 | 244 | $this->_nodes[$offset] = $connection; 245 | $this->_weights[$offset] = 1; 246 | } 247 | 248 | /** 249 | * Unset an offset 250 | * 251 | * @param int $offset 252 | */ 253 | public function offsetUnset($offset) 254 | { 255 | unset($this->_nodes[$offset]); 256 | unset($this->_weights[$offset]); 257 | } 258 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Connection/StackTest.php: -------------------------------------------------------------------------------- 1 | _stack = new Shanty_Mongo_Connection_Stack(); 13 | } 14 | 15 | public function testOptionGetAndSet() 16 | { 17 | $this->assertTrue($this->_stack->getOption('cacheConnectionSelection')); 18 | $this->assertNull($this->_stack->getOption('option does not exist')); 19 | 20 | $this->_stack->setOption('cacheConnectionSelection', false); 21 | $this->assertFalse($this->_stack->getOption('cacheConnectionSelection')); 22 | 23 | $options = array( 24 | 'cacheConnectionSelection' => true 25 | ); 26 | 27 | $this->_stack->setOptions($options); 28 | $this->assertTrue($this->_stack->getOption('cacheConnectionSelection')); 29 | } 30 | 31 | public function testCacheConnectionSelection() 32 | { 33 | $this->assertTrue($this->_stack->cacheConnectionSelection()); 34 | $this->_stack->cacheConnectionSelection(false); 35 | $this->assertFalse($this->_stack->cacheConnectionSelection()); 36 | } 37 | 38 | public function testAddAndCountNodes() 39 | { 40 | $this->assertEquals(0, count($this->_stack)); 41 | 42 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 43 | $this->assertEquals(1, count($this->_stack)); 44 | 45 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 46 | $this->assertEquals(2, count($this->_stack)); 47 | } 48 | 49 | public function testNoNodes() 50 | { 51 | $this->assertEquals(0, count($this->_stack)); 52 | $this->assertNull($this->_stack->selectNode()); 53 | } 54 | 55 | /** 56 | * @depends testAddAndCountNodes 57 | */ 58 | public function testSelectNode() 59 | { 60 | $connection = $this->getMock('Shanty_Mongo_Connection'); 61 | $this->_stack->addNode($connection); 62 | $this->assertEquals($connection, $this->_stack->selectNode()); 63 | } 64 | 65 | /** 66 | * @depends testCacheConnectionSelection 67 | * @depends testAddAndCountNodes 68 | * @depends testSelectNode 69 | */ 70 | public function testCacheConnection() 71 | { 72 | $this->_stack->cacheConnectionSelection(true); 73 | 74 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 75 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 76 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 77 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 78 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 79 | 80 | $connection = $this->_stack->selectNode(); 81 | for ($i = 0; $i < 100; $i++) { 82 | if ($this->_stack->selectNode() != $connection) { 83 | $this->fail('Connection was not cached property'); 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * @depends testCacheConnectionSelection 90 | * @depends testAddAndCountNodes 91 | * @depends testSelectNode 92 | */ 93 | public function testNodeWeight() 94 | { 95 | $this->_stack->cacheConnectionSelection(false); 96 | 97 | $nodes = array(); 98 | $nodes[] = array('connection' => $this->getMock('Shanty_Mongo_Connection'), 'weight' => 100, 'selected' => 0); 99 | $nodes[] = array('connection' => $this->getMock('Shanty_Mongo_Connection'), 'weight' => 300, 'selected' => 0); 100 | $nodes[] = array('connection' => $this->getMock('Shanty_Mongo_Connection'), 'weight' => 600, 'selected' => 0); 101 | 102 | foreach ($nodes as $node) { 103 | $this->_stack->addNode($node['connection'], $node['weight']); 104 | } 105 | 106 | for ($i = 0; $i < 1000; $i++) { 107 | $selectedNode = $this->_stack->selectNode(); 108 | 109 | foreach ($nodes as $key => $node) { 110 | if ($node['connection'] !== $selectedNode) continue; 111 | 112 | $nodes[$key]['selected'] += 1; 113 | break; 114 | } 115 | } 116 | 117 | foreach ($nodes as $node) { 118 | $this->assertThat( 119 | $node['selected'], 120 | $this->logicalAnd( 121 | $this->greaterThan($node['weight'] - 100), 122 | $this->lessThan($node['weight'] + 100) 123 | ) 124 | ); 125 | } 126 | } 127 | 128 | /** 129 | * @depends testAddAndCountNodes 130 | */ 131 | public function testNodeIterate() 132 | { 133 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 134 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 135 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 136 | $this->_stack->addNode($this->getMock('Shanty_Mongo_Connection')); 137 | 138 | $counter = 0; 139 | foreach ($this->_stack as $node) { 140 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $node); 141 | $counter += 1; 142 | } 143 | 144 | $this->assertEquals(4, $counter); 145 | 146 | $this->assertEquals(4, $this->_stack->key()); 147 | $this->assertFalse($this->_stack->valid()); 148 | 149 | $this->_stack->rewind(); 150 | 151 | $this->assertEquals(0, $this->_stack->key()); 152 | 153 | $this->_stack->seek(2); 154 | $this->assertEquals(2, $this->_stack->key()); 155 | 156 | } 157 | 158 | /** 159 | * @expectedException Shanty_Mongo_Exception 160 | */ 161 | public function testSeekNonNumericException() 162 | { 163 | $this->_stack->seek('asdfasdf'); 164 | } 165 | 166 | /** 167 | * @expectedException OutOfBoundsException 168 | */ 169 | public function testSeekOutOfBoundsException() 170 | { 171 | $this->_stack->seek(100); 172 | } 173 | 174 | public function testArrayAccess() 175 | { 176 | $this->assertFalse($this->_stack->offsetExists(100)); 177 | 178 | $connection1 = $this->getMock('Shanty_Mongo_Connection'); 179 | $connection2 = $this->getMock('Shanty_Mongo_Connection'); 180 | $this->_stack[0] = $connection1; 181 | $this->_stack[1] = $connection2; 182 | 183 | $this->assertEquals(2, count($this->_stack)); 184 | 185 | $this->assertEquals($connection1, $this->_stack[0]); 186 | $this->assertEquals($connection2, $this->_stack[1]); 187 | $this->assertNull($this->_stack[2]); 188 | 189 | unset($this->_stack[0]); 190 | $this->assertNull($this->_stack[0]); 191 | $this->assertEquals(1, count($this->_stack)); 192 | } 193 | 194 | /** 195 | * @expectedException Shanty_Mongo_Exception 196 | */ 197 | public function testOffsetSetNonNumericException() 198 | { 199 | $this->_stack['asdfasdf'] = $this->getMock('Shanty_Mongo_Connection'); 200 | } 201 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Connection/Group.php: -------------------------------------------------------------------------------- 1 | _masters = new Shanty_Mongo_Connection_Stack(); 21 | $this->_slaves = new Shanty_Mongo_Connection_Stack(); 22 | 23 | // add connections 24 | if (!is_null($connectionOptions)) { 25 | $this->addConnections($connectionOptions); 26 | } 27 | } 28 | 29 | /** 30 | * Add multiple connections at once using arrays of options 31 | * 32 | * @param array $connectionOptions 33 | */ 34 | public function addConnections($connectionOptions) 35 | { 36 | if ($connectionOptions instanceof Zend_Config) { 37 | $connectionOptions = $connectionOptions->toArray(); 38 | } 39 | 40 | $masters = array(); 41 | $masterStackOptions = array(); 42 | $slaves = array(); 43 | $slaveStackOptions = array(); 44 | 45 | $group = $this; 46 | $addConnections = function(Shanty_Mongo_Connection_Stack $stack, array $connections) use ($group) { 47 | foreach ($connections as $connectionData) { 48 | $options = array_intersect_key($connectionData, array_flip(Shanty_Mongo_Connection::getAvailableOptions())); 49 | 50 | $connection = new Shanty_Mongo_Connection($group->formatConnectionString($connectionData), $options); 51 | if (array_key_exists('weight', $connectionData)) $weight = (int) $connectionData['weight']; 52 | else $weight = 1; 53 | 54 | $stack->addNode($connection, $weight); 55 | } 56 | }; 57 | 58 | // Lets add our masters 59 | if (array_key_exists('master', $connectionOptions)) $masters[] = $connectionOptions['master']; // single master 60 | elseif (array_key_exists('masters', $connectionOptions)) { 61 | $connectionKeys = array_filter(array_keys($connectionOptions['masters']), 'is_numeric'); 62 | $masters = array_intersect_key($connectionOptions['masters'], array_flip($connectionKeys)); // only connections 63 | $masterStackOptions = array_diff_key($connectionOptions['masters'], array_flip($connectionKeys)); // only options 64 | } 65 | else $masters[] = $connectionOptions; // one server 66 | 67 | $addConnections($this->getMasters(), $masters); // Add master connections 68 | $this->getMasters()->setOptions($masterStackOptions); // Set master stack options 69 | 70 | // Lets add our slaves 71 | if (array_key_exists('slave', $connectionOptions)) $slaves[] = $connectionOptions['slave']; // single slave 72 | elseif (array_key_exists('slaves', $connectionOptions)) { 73 | $connectionKeys = array_filter(array_keys($connectionOptions['slaves']), 'is_numeric'); 74 | $slaves = array_intersect_key($connectionOptions['slaves'], array_flip($connectionKeys)); // only connections 75 | $slaveStackOptions = array_diff_key($connectionOptions['slaves'], array_flip($connectionKeys)); // only options 76 | }; 77 | 78 | $addConnections($this->getSlaves(), $slaves); // Add slave connections 79 | $this->getSlaves()->setOptions($slaveStackOptions); // Set slave stack options 80 | } 81 | 82 | /** 83 | * Add a connection to a master server 84 | * 85 | * @param Shanty_Mongo_Connection $connection 86 | * @param int $weight 87 | */ 88 | public function addMaster(Shanty_Mongo_Connection $connection, $weight = 1) 89 | { 90 | $this->_masters->addNode($connection, $weight); 91 | } 92 | 93 | /** 94 | * Get all master connections 95 | * 96 | * @return Shanty_Mongo_Connection_Stack 97 | */ 98 | public function getMasters() 99 | { 100 | return $this->_masters; 101 | } 102 | 103 | /** 104 | * Add a connection to a slaver server 105 | * 106 | * @param $connection 107 | * @param $weight 108 | */ 109 | public function addSlave(Shanty_Mongo_Connection $connection, $weight = 1) 110 | { 111 | $this->_slaves->addNode($connection, $weight); 112 | } 113 | 114 | /** 115 | * Get all slave connections 116 | * 117 | * @return Shanty_Mongo_Connection_Stack 118 | */ 119 | public function getSlaves() 120 | { 121 | return $this->_slaves; 122 | } 123 | 124 | /** 125 | * Get a write connection 126 | * 127 | * @return Shanty_Mongo_Connection 128 | */ 129 | public function getWriteConnection() 130 | { 131 | // Select master 132 | $write = $this->_masters->selectNode(); 133 | if ($write && !$write->connected) { 134 | $write->connect(); 135 | } 136 | 137 | return $write; 138 | } 139 | 140 | /** 141 | * Get a read connection 142 | * 143 | * @return Shanty_Mongo_Connection 144 | */ 145 | public function getReadConnection() 146 | { 147 | if (count($this->_slaves) === 0) { 148 | // If no slaves then get a master connection 149 | $read = $this->getWriteConnection(); 150 | } 151 | else { 152 | // Select slave 153 | $read = $this->_slaves->selectNode(); 154 | if ($read) $read->connect(); 155 | } 156 | 157 | return $read; 158 | } 159 | 160 | /** 161 | * Format a connection string 162 | * 163 | * @param array $connectionOptions 164 | * 165 | */ 166 | public function formatConnectionString(array $connectionOptions = array()) 167 | { 168 | // See if we are dealing with a replica set 169 | if (array_key_exists('hosts', $connectionOptions)) $hosts = $connectionOptions['hosts']; 170 | else $hosts = array($connectionOptions); 171 | 172 | $connectionString = 'mongodb://'; 173 | 174 | $hostStringList = array(); 175 | foreach ($hosts as $hostOptions) { 176 | $hostStringList[] = static::formatHostString($hostOptions); 177 | } 178 | 179 | $connectionString .= implode(',', $hostStringList); 180 | 181 | // Set database 182 | if (isset($connectionOptions['database'])) $connectionString .= '/'.$connectionOptions['database']; 183 | 184 | return $connectionString; 185 | } 186 | 187 | /** 188 | * Format a host string 189 | * 190 | * @param $options 191 | * @return string 192 | */ 193 | public function formatHostString(array $hostOptions = array()) 194 | { 195 | $hostString = ''; 196 | 197 | // Set username 198 | if (isset($hostOptions['username']) && !is_null($hostOptions['username'])) { 199 | $hostString .= $hostOptions['username']; 200 | 201 | // Set password 202 | if (isset($hostOptions['password']) && !is_null($hostOptions['password'])) { 203 | $hostString .= ':'.$hostOptions['password']; 204 | } 205 | 206 | $hostString .= '@'; 207 | } 208 | 209 | // Set host 210 | if (isset($hostOptions['host']) && !is_null($hostOptions['host'])) $hostString .= $hostOptions['host']; 211 | else $hostString .= '127.0.0.1'; 212 | 213 | // Set port 214 | $hostString .= ':'; 215 | if (isset($hostOptions['port']) && !is_null($hostOptions['port'])) $hostString .= $hostOptions['port']; 216 | else $hostString .= '27017'; 217 | 218 | return $hostString; 219 | } 220 | } -------------------------------------------------------------------------------- /tests/Shanty/TestSetup.php: -------------------------------------------------------------------------------- 1 | _useMyIncludePath(); 38 | 39 | require_once 'My/ShantyMongo/Country.php'; 40 | require_once 'My/ShantyMongo/User.php'; 41 | require_once 'My/ShantyMongo/Users.php'; 42 | require_once 'My/ShantyMongo/Name.php'; 43 | require_once 'My/ShantyMongo/Student.php'; 44 | require_once 'My/ShantyMongo/ArtStudent.php'; 45 | require_once 'My/ShantyMongo/Teacher.php'; 46 | require_once 'My/ShantyMongo/Article.php'; 47 | require_once 'My/ShantyMongo/InvalidDocument.php'; 48 | require_once 'My/ShantyMongo/Simple.php'; 49 | require_once 'My/ShantyMongo/SimpleWithExport.php'; 50 | require_once 'My/ShantyMongo/SimpleSubDocWithExport.php'; 51 | 52 | $this->_connection = new Shanty_Mongo_Connection(TESTS_SHANTY_MONGO_CONNECTIONSTRING); 53 | $this->_connection->connect(); 54 | Shanty_Mongo::addMaster($this->_connection); 55 | 56 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('user')->drop(); 57 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('article')->drop(); 58 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('simple')->drop(); 59 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('country')->drop(); 60 | $this->populateDb(); 61 | } 62 | 63 | public function populateDb() 64 | { 65 | $this->_users = array( 66 | 'bob' => array( 67 | '_id' => new MongoId('4c04516a1f5f5e21361e3ab0'), 68 | '_type' => array( 69 | 'My_ShantyMongo_Teacher', 70 | 'My_ShantyMongo_User' 71 | ), 72 | 'name' => array( 73 | 'first' => 'Bob', 74 | 'last' => 'Jones', 75 | ), 76 | 'addresses' => array( 77 | array( 78 | 'street' => '19 Hill St', 79 | 'suburb' => 'Brisbane', 80 | 'state' => 'QLD', 81 | 'postcode' => '4000', 82 | 'country' => 'Australia' 83 | ), 84 | array( 85 | 'street' => '742 Evergreen Terrace', 86 | 'suburb' => 'Springfield', 87 | 'state' => 'Nevada', 88 | 'postcode' => '89002', 89 | 'country' => 'USA' 90 | ) 91 | ), 92 | 'friends' => array( 93 | MongoDBRef::create('user', new MongoId('4c04516f1f5f5e21361e3ab1')), 94 | MongoDBRef::create('user', new MongoId('4c0451791f5f5e21361e3ab2')), 95 | MongoDBRef::create('user', new MongoId('000000000000000000000000')), 96 | ), 97 | 'faculty' => 'Maths', 98 | 'email' => 'bob.jones@domain.com', 99 | 'sex' => 'M', 100 | 'partner' => MongoDBRef::create('user', new MongoId('4c04516f1f5f5e21361e3ab1')), 101 | 'bestFriend' => MongoDBRef::create('user', new MongoId('4c0451791f5f5e21361e3ab2')) // To test Shanty_Mongo_Document::isReference() 102 | ), 103 | 'cherry' => array( 104 | '_id' => new MongoId('4c04516f1f5f5e21361e3ab1'), 105 | '_type' => array( 106 | 'My_ShantyMongo_Student', 107 | 'My_ShantyMongo_User' 108 | ), 109 | 'name' => array( 110 | 'first' => 'Cherry', 111 | 'last' => 'Jones', 112 | ), 113 | 'email' => 'cherry.jones@domain.com', 114 | 'sex' => 'F', 115 | 'concession' => true 116 | ), 117 | 'roger' => array( 118 | '_id' => new MongoId('4c0451791f5f5e21361e3ab2'), 119 | '_type' => array( 120 | 'My_ShantyMongo_ArtStudent', 121 | 'My_ShantyMongo_Student', 122 | 'My_ShantyMongo_User' 123 | ), 124 | 'name' => array( 125 | 'first' => 'Roger', 126 | 'last' => 'Smith', 127 | ), 128 | 'email' => 'roger.smith@domain.com', 129 | 'sex' => 'M', 130 | 'concession' => false 131 | ), 132 | ); 133 | 134 | $this->_userCollection = $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('user'); 135 | 136 | foreach ($this->_users as $user) { 137 | $this->_userCollection->insert($user, array('safe' => true)); 138 | } 139 | 140 | $this->_articles = array( 141 | 'regular' => array( 142 | '_id' => new MongoId('4c04516f1f5f5e21361e3ac1'), 143 | 'title' => 'How to use Shanty Mongo', 144 | 'author' => MongoDBRef::create('user', new MongoId('4c04516a1f5f5e21361e3ab0')), 145 | 'editor' => MongoDBRef::create('user', new MongoId('4c04516f1f5f5e21361e3ab1')), 146 | 'contributors' => array( 147 | MongoDBRef::create('user', new MongoId('4c04516f1f5f5e21361e3ab1')), 148 | MongoDBRef::create('user', new MongoId('4c0451791f5f5e21361e3ab2')), 149 | ), 150 | 'relatedArticles' => array( 151 | MongoDBRef::create('article', new MongoId('4c04516f1f5f5e21361e3ac2')), 152 | ), 153 | 'tags' => array('awesome', 'howto', 'mongodb') 154 | ), 155 | 'broken' => array( 156 | '_id' => new MongoId('4c04516f1f5f5e21361e3ac2'), 157 | 'title' => 'How to use Bend Space and Time', 158 | 'author' => MongoDBRef::create('user', new MongoId('000000000000000000000000')), 159 | 'tags' => array('physics', 'hard', 'cool') 160 | ) 161 | ); 162 | 163 | $this->_articleCollection = $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('article'); 164 | 165 | foreach ($this->_articles as $article) { 166 | $this->_articleCollection->insert($article, array('safe' => true)); 167 | } 168 | 169 | // Insert some countries 170 | require_once 'Zend/Json.php'; 171 | 172 | $countries = Zend_Json::decode(file_get_contents($this->getFilesDir().'/countries.json')); 173 | My_ShantyMongo_Country::insertBatch($countries); 174 | } 175 | 176 | public function tearDown() 177 | { 178 | $this->_restoreIncludePath(); 179 | 180 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('user')->drop(); 181 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('article')->drop(); 182 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('simple')->drop(); 183 | $this->_connection->selectDb(TESTS_SHANTY_MONGO_DB)->selectCollection('country')->drop(); 184 | 185 | Shanty_Mongo::makeClean(); 186 | 187 | parent::tearDown(); 188 | } 189 | 190 | protected function _useMyIncludePath() 191 | { 192 | $this->_runtimeIncludePath = get_include_path(); 193 | set_include_path(dirname(__FILE__) . '/Mongo/_files/' . PATH_SEPARATOR . $this->_runtimeIncludePath); 194 | } 195 | 196 | protected function _restoreIncludePath() 197 | { 198 | set_include_path($this->_runtimeIncludePath); 199 | $this->_runtimeIncludePath = null; 200 | } 201 | 202 | protected function getFilesDir() 203 | { 204 | return __DIR__ . '/Mongo/_files'; 205 | } 206 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/DocumentSet.php: -------------------------------------------------------------------------------- 1 | 'Document' 16 | ); 17 | 18 | /** 19 | * Get the property keys for this Document Set 20 | * 21 | * @return array 22 | */ 23 | public function getPropertyKeys() 24 | { 25 | $keys = parent::getPropertyKeys(); 26 | sort($keys, SORT_NUMERIC); 27 | 28 | return $keys; 29 | } 30 | 31 | /** 32 | * Get a property 33 | * 34 | * @param mixed $property 35 | */ 36 | public function getProperty($index = null) 37 | { 38 | $new = is_null($index); 39 | 40 | // If property exists and initialised then return it 41 | if (!$new && array_key_exists($index, $this->_data)) { 42 | return $this->_data[$index]; 43 | } 44 | 45 | // Make sure we are not trying to create a document that is supposed to be saved as a reference 46 | if ($new && $this->hasRequirement(static::DYNAMIC_INDEX, 'AsReference')) { 47 | require_once 'Shanty/Mongo/Exception.php'; 48 | throw new Shanty_Mongo_Exception("Can not create a new document from documentset where document must be saved as a reference"); 49 | } 50 | 51 | if (!$new) { 52 | // Fetch clean data for this property if it exists 53 | if (array_key_exists($index, $this->_cleanData)) $data = $this->_cleanData[$index]; 54 | else return null; 55 | } 56 | else $data = array(); 57 | 58 | // If property is a reference to another document then fetch the reference document 59 | if (MongoDBRef::isRef($data)) { 60 | $collection = $data['$ref']; 61 | $data = MongoDBRef::get($this->_getMongoDB(false), $data); 62 | 63 | // If this is a broken reference then no point keeping it for later 64 | if (!$data) { 65 | $this->_data[$index] = null; 66 | return $this->_data[$index]; 67 | } 68 | 69 | $reference = true; 70 | } 71 | else { 72 | $reference = false; 73 | $collection = $this->getConfigAttribute('collection'); 74 | } 75 | 76 | $config = array (); 77 | $config['new'] = $new; 78 | $config['requirementModifiers'] = $this->getRequirements(self::DYNAMIC_INDEX.'.'); 79 | $config['parentIsDocumentSet'] = true; 80 | $config['connectionGroup'] = $this->getConfigAttribute('connectionGroup'); 81 | $config['db'] = $this->getConfigAttribute('db'); 82 | $config['collection'] = $collection; 83 | 84 | if (!$reference) { 85 | // If this is a new array element. We will $push to the array when saving 86 | if ($new) $path = $this->getPathToDocument(); 87 | else $path = $this->getPathToProperty($index); 88 | 89 | $config['pathToDocument'] = $path; 90 | $config['criteria'] = $this->getCriteria(); 91 | $config['hasId'] = $this->hasRequirement(self::DYNAMIC_INDEX, 'hasId'); 92 | } 93 | 94 | // get the document class 95 | $documentClass = $this->hasRequirement(self::DYNAMIC_INDEX, 'Document'); 96 | if (isset($data['_type']) && !empty($data['_type'][0])) { 97 | $documentClass = $data['_type'][0]; 98 | } 99 | $document = new $documentClass($data, $config); 100 | 101 | // if this document was a reference then remember that 102 | if ($reference) { 103 | $this->_references->attach($document); 104 | } 105 | 106 | // If this is not a new document cache it 107 | if (!$new) { 108 | $this->_data[$index] = $document; 109 | } 110 | 111 | return $document; 112 | } 113 | 114 | /** 115 | * Set property 116 | * 117 | * @param $index 118 | * @param $document 119 | */ 120 | public function setProperty($index, $document) 121 | { 122 | $new = is_null($index); 123 | 124 | // Make sure index is numeric 125 | if (!$new && !is_numeric($index)) { 126 | require_once 'Shanty/Mongo/Exception.php'; 127 | throw new Shanty_Mongo_Exception("Index must be numeric '{$index}' given"); 128 | } 129 | 130 | // Unset element 131 | if (!$new && is_null($document)) { 132 | $this->_data[$index] = null; 133 | return; 134 | } 135 | 136 | // Make sure we are not keeping a copy of the old document in reference memory 137 | if (!$new && isset($this->_data[$index]) && !is_null($this->_data[$index])) { 138 | $this->_references->detach($this->_data[$index]); 139 | } 140 | 141 | // Throw exception if value is not valid 142 | $validators = $this->getValidators(self::DYNAMIC_INDEX); 143 | 144 | if (!$validators->isValid($document)) { 145 | require_once 'Shanty/Mongo/Exception.php'; 146 | throw new Shanty_Mongo_Exception(implode($validators->getMessages(), "\n")); 147 | } 148 | 149 | if ($new) { 150 | $keys = $this->getPropertyKeys(); 151 | $index = empty($keys) ? 0 : max($keys)+1; 152 | } 153 | 154 | // Filter value 155 | // $value = $this->getFilters(self::DYNAMIC_INDEX)->filter($document); 156 | 157 | if (!$this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) { 158 | // Make a new document if it has been saved somewhere else 159 | if (!$document->isNewDocument()) { 160 | $documentClass = get_class($document); 161 | $document = new $documentClass($document->export(), array('new' => false, 'pathToDocument' => $this->getPathToProperty($index))); 162 | } 163 | else { 164 | $document->setPathToDocument($this->getPathToProperty($index)); 165 | } 166 | 167 | // Inform the document of it's surroundings 168 | $document->setConfigAttribute('connectionGroup', $this->getConfigAttribute('connectionGroup')); 169 | $document->setConfigAttribute('db', $this->getConfigAttribute('db')); 170 | $document->setConfigAttribute('collection', $this->getConfigAttribute('collection')); 171 | $document->setConfigAttribute('criteria', $this->getCriteria()); 172 | $document->applyRequirements($this->getRequirements(self::DYNAMIC_INDEX.'.')); 173 | } 174 | 175 | $this->_data[$index] = $document; 176 | } 177 | 178 | /** 179 | * Export all data 180 | * 181 | * @return array 182 | */ 183 | public function export($skipRequired = false) 184 | { 185 | // Since this is an array, fill in empty index's with null 186 | $exportData = parent::export($skipRequired); 187 | 188 | // Fix PHP "max(): Array must contain at least one element" bug 189 | // if DocumentSet has no data 190 | if (count($exportData) > 0) { 191 | $maxKey = max(array_keys($exportData)); 192 | 193 | for ($i = 0; $i<$maxKey; $i++) { 194 | if (array_key_exists($i, $exportData)) { 195 | continue; 196 | } 197 | 198 | $exportData[$i] = null; 199 | } 200 | 201 | ksort($exportData); 202 | } 203 | 204 | return $exportData; 205 | } 206 | 207 | /** 208 | * Add a document to this set 209 | * 210 | * @param Shanty_Mongo_Document $document 211 | */ 212 | public function addDocument(Shanty_Mongo_Document $document) 213 | { 214 | return $this->setProperty(null, $document); 215 | } 216 | 217 | /** 218 | * Add a document to the push queue 219 | * 220 | * @param Shanty_Mongo_Document $document 221 | */ 222 | public function pushDocument(Shanty_Mongo_Document $document) 223 | { 224 | $this->push(null, $document); 225 | } 226 | 227 | /** 228 | * Get all operations 229 | * 230 | * @param Boolean $includingChildren Get operations from children as well 231 | */ 232 | public function getOperations($includingChildren = false) 233 | { 234 | if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false; 235 | 236 | return parent::getOperations($includingChildren); 237 | } 238 | 239 | /** 240 | * Remove all operations 241 | * 242 | * @param Boolean $includingChildren Remove operations from children as wells 243 | */ 244 | public function purgeOperations($includingChildren = false) 245 | { 246 | if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false; 247 | 248 | return parent::purgeOperations($includingChildren); 249 | } 250 | 251 | public function __call($name, $arguments = array()) 252 | { 253 | switch ($name) { 254 | case 'new': 255 | return $this->getProperty(); 256 | } 257 | 258 | require_once 'Shanty/Mongo/Exception.php'; 259 | throw new Shanty_Mongo_Exception("Captured in __call. Method $name does not exist."); 260 | } 261 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/Connection/GroupTest.php: -------------------------------------------------------------------------------- 1 | _group = new Shanty_Mongo_Connection_Group(); 18 | } 19 | 20 | public function testAddConnectionsNewGroupWithConnections() 21 | { 22 | $connections = array( 23 | 'master' => array('host' => 'localhost', 'connectTimeoutMS' => 300), 24 | 'slave' => array('host' => '127.0.0.1'), 25 | ); 26 | 27 | $group = new Shanty_Mongo_Connection_Group($connections); 28 | $this->assertEquals(1, count($group->getMasters())); 29 | $this->assertEquals(1, count($group->getSlaves())); 30 | 31 | $masters = $group->getMasters(); 32 | $slaves = $group->getSlaves(); 33 | 34 | $masterInfo = $masters[0]->getConnectionInfo(); 35 | $this->assertEquals('mongodb://localhost:27017', $masterInfo['connectionString']); 36 | $this->assertEquals(300, $masterInfo['connectTimeoutMS']); 37 | 38 | $slave1Info = $slaves[0]->getConnectionInfo(); 39 | $this->assertEquals('mongodb://127.0.0.1:27017', $slave1Info['connectionString']); 40 | } 41 | 42 | public function testAddConnectionsSingleServer() 43 | { 44 | $connections = array('host' => 'localhost'); 45 | $this->_group->addConnections($connections); 46 | $this->assertEquals(1, count($this->_group->getMasters())); 47 | $this->assertEquals(0, count($this->_group->getSlaves())); 48 | 49 | $writeConnection = $this->_group->getWriteConnection(); 50 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $writeConnection); 51 | 52 | $writeConnectionInfo = $writeConnection->getConnectionInfo(); 53 | $this->assertEquals('mongodb://localhost:27017', $writeConnectionInfo['connectionString']); 54 | 55 | // Make sure read and write connections are the same 56 | $this->assertEquals($writeConnection, $this->_group->getReadConnection()); 57 | } 58 | 59 | public function testAddConnectionsSingleMasterSingleSlave() 60 | { 61 | $connections = array( 62 | 'master' => array('host' => 'localhost'), 63 | 'slave' => array('host' => '127.0.0.1'), 64 | ); 65 | 66 | $this->_group->addConnections($connections); 67 | $this->assertEquals(1, count($this->_group->getMasters())); 68 | $this->assertEquals(1, count($this->_group->getSlaves())); 69 | 70 | $masters = $this->_group->getMasters(); 71 | $slaves = $this->_group->getSlaves(); 72 | 73 | $masterInfo = $masters[0]->getConnectionInfo(); 74 | $this->assertEquals('mongodb://localhost:27017', $masterInfo['connectionString']); 75 | 76 | $slave1Info = $slaves[0]->getConnectionInfo(); 77 | $this->assertEquals('mongodb://127.0.0.1:27017', $slave1Info['connectionString']); 78 | } 79 | 80 | public function testAddConnectionsMultiMasterMultiSlave() 81 | { 82 | $connections = array( 83 | 'masters' => array( 84 | 0 => array('host' => '127.0.0.1'), 85 | 'cacheConnectionSelection' => true, 86 | 1 => array('host' => 'localhost'), 87 | ), 88 | 'slaves' => array( 89 | 0 => array('host' => '127.0.0.1'), 90 | 'cacheConnectionSelection' => true, 91 | 1 => array('host' => 'localhost') 92 | ) 93 | ); 94 | 95 | $this->_group->addConnections($connections); 96 | $this->assertEquals(2, count($this->_group->getMasters())); 97 | $this->assertEquals(2, count($this->_group->getSlaves())); 98 | 99 | $masters = $this->_group->getMasters(); 100 | $slaves = $this->_group->getSlaves(); 101 | 102 | $master1Info = $masters[0]->getConnectionInfo(); 103 | $this->assertEquals('mongodb://127.0.0.1:27017', $master1Info['connectionString']); 104 | 105 | $master2Info = $masters[1]->getConnectionInfo(); 106 | $this->assertEquals('mongodb://localhost:27017', $master2Info['connectionString']); 107 | 108 | $slave1Info = $slaves[0]->getConnectionInfo(); 109 | $this->assertEquals('mongodb://127.0.0.1:27017', $slave1Info['connectionString']); 110 | 111 | $slave2Info = $slaves[1]->getConnectionInfo(); 112 | $this->assertEquals('mongodb://localhost:27017', $slave2Info['connectionString']); 113 | } 114 | 115 | public function testAddConnectionsZendConfig() 116 | { 117 | $connections = array( 118 | 'master' => array('host' => 'localhost'), 119 | 'slave' => array('host' => '127.0.0.1'), 120 | ); 121 | 122 | $this->_group->addConnections(new Zend_Config($connections)); 123 | $this->assertEquals(1, count($this->_group->getMasters())); 124 | $this->assertEquals(1, count($this->_group->getSlaves())); 125 | 126 | $masters = $this->_group->getMasters(); 127 | $slaves = $this->_group->getSlaves(); 128 | 129 | $masterInfo = $masters[0]->getConnectionInfo(); 130 | $this->assertEquals('mongodb://localhost:27017', $masterInfo['connectionString']); 131 | 132 | $slave1Info = $slaves[0]->getConnectionInfo(); 133 | $this->assertEquals('mongodb://127.0.0.1:27017', $slave1Info['connectionString']); 134 | } 135 | 136 | public function testAddAndGetMasters() 137 | { 138 | $this->assertEquals(0, count($this->_group->getMasters())); 139 | $this->_group->addMaster($this->getMock('Shanty_Mongo_Connection')); 140 | $this->_group->addMaster($this->getMock('Shanty_Mongo_Connection')); 141 | $this->assertEquals(2, count($this->_group->getMasters())); 142 | } 143 | 144 | public function testAddAndGetSlaves() 145 | { 146 | $this->assertEquals(0, count($this->_group->getSlaves())); 147 | $this->_group->addSlave($this->getMock('Shanty_Mongo_Connection')); 148 | $this->_group->addSlave($this->getMock('Shanty_Mongo_Connection')); 149 | $this->assertEquals(2, count($this->_group->getSlaves())); 150 | } 151 | 152 | public function testGetWriteConnection() 153 | { 154 | $master = $this->getMock('Shanty_Mongo_Connection', array('connect')); 155 | $this->_group->addMaster($master); 156 | $this->assertEquals($master, $this->_group->getWriteConnection()); 157 | } 158 | 159 | public function testGetReadConnection() 160 | { 161 | // Test no slaves, only master 162 | $master = $this->getMock('Shanty_Mongo_Connection', array('connect')); 163 | $this->_group->addMaster($master); 164 | 165 | $this->assertEquals($master, $this->_group->getReadConnection()); 166 | 167 | // Test slaves plus master 168 | $slave = $this->getMock('Shanty_Mongo_Connection', array('connect')); 169 | $this->_group->addSlave($slave); 170 | 171 | $this->assertEquals($slave, $this->_group->getReadConnection()); 172 | } 173 | 174 | public function testFormatConnectionString() 175 | { 176 | $this->assertEquals('mongodb://127.0.0.1:27017', $this->_group->formatConnectionString()); 177 | 178 | $options = array('host' => 'mongodb.local'); 179 | $this->assertEquals("mongodb://{$options['host']}:27017", $this->_group->formatConnectionString($options)); 180 | 181 | $options = array( 182 | 'hosts' => array( 183 | array('host' => 'mongodb1.local'), 184 | array('host' => 'mongodb2.local', 'port' => 27018), 185 | array('host' => 'mongodb3.local'), 186 | ), 187 | 'database' => 'shanty-mongo' 188 | ); 189 | $this->assertEquals("mongodb://{$options['hosts'][0]['host']}:27017,{$options['hosts'][1]['host']}:27018,{$options['hosts'][2]['host']}:27017/shanty-mongo", $this->_group->formatConnectionString($options)); 190 | } 191 | 192 | public function testFormatHostString() 193 | { 194 | $this->assertEquals('127.0.0.1:27017', $this->_group->formatHostString()); 195 | 196 | $options = array('host' => 'mongodb.local'); 197 | $this->assertEquals("{$options['host']}:27017", $this->_group->formatHostString($options)); 198 | 199 | $options = array('port' => '27018'); 200 | $this->assertEquals("127.0.0.1:{$options['port']}", $this->_group->formatHostString($options)); 201 | 202 | $options = array('username' => 'jerry', 'password' => 'springer'); 203 | $this->assertEquals("{$options['username']}:{$options['password']}@127.0.0.1:27017", $this->_group->formatHostString($options)); 204 | } 205 | 206 | public function testReplicaSet() 207 | { 208 | $connections = array( 209 | 'hosts' => array( 210 | array('host' => 'mongodb1.local'), 211 | array('host' => 'mongodb2.local'), 212 | array('host' => 'mongodb3.local'), 213 | ), 214 | 'replicaSet' => true 215 | ); 216 | 217 | $group = new Shanty_Mongo_Connection_Group($connections); 218 | $this->assertEquals(1, count($group->getMasters())); 219 | $this->assertEquals(0, count($group->getSlaves())); 220 | 221 | $masters = $group->getMasters(); 222 | 223 | $masterInfo = $masters[0]->getConnectionInfo(); 224 | $this->assertEquals("mongodb://{$connections['hosts'][0]['host']}:27017,{$connections['hosts'][1]['host']}:27017,{$connections['hosts'][2]['host']}:27017", $masterInfo['connectionString']); 225 | $this->assertTrue($masterInfo['replicaSet']); 226 | } 227 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/_files/countries.json: -------------------------------------------------------------------------------- 1 | [{"code":"US", "name":"United States"}, 2 | {"code":"CA", "name":"Canada"}, 3 | {"code":"AF", "name":"Afghanistan"}, 4 | {"code":"AL", "name":"Albania"}, 5 | {"code":"DZ", "name":"Algeria"}, 6 | {"code":"DS", "name":"American Samoa"}, 7 | {"code":"AD", "name":"Andorra"}, 8 | {"code":"AO", "name":"Angola"}, 9 | {"code":"AI", "name":"Anguilla"}, 10 | {"code":"AQ", "name":"Antarctica"}, 11 | {"code":"AG", "name":"Antigua and/or Barbuda"}, 12 | {"code":"AR", "name":"Argentina"}, 13 | {"code":"AM", "name":"Armenia"}, 14 | {"code":"AW", "name":"Aruba"}, 15 | {"code":"AU", "name":"Australia"}, 16 | {"code":"AT", "name":"Austria"}, 17 | {"code":"AZ", "name":"Azerbaijan"}, 18 | {"code":"BS", "name":"Bahamas"}, 19 | {"code":"BH", "name":"Bahrain"}, 20 | {"code":"BD", "name":"Bangladesh"}, 21 | {"code":"BB", "name":"Barbados"}, 22 | {"code":"BY", "name":"Belarus"}, 23 | {"code":"BE", "name":"Belgium"}, 24 | {"code":"BZ", "name":"Belize"}, 25 | {"code":"BJ", "name":"Benin"}, 26 | {"code":"BM", "name":"Bermuda"}, 27 | {"code":"BT", "name":"Bhutan"}, 28 | {"code":"BO", "name":"Bolivia"}, 29 | {"code":"BA", "name":"Bosnia and Herzegovina"}, 30 | {"code":"BW", "name":"Botswana"}, 31 | {"code":"BV", "name":"Bouvet Island"}, 32 | {"code":"BR", "name":"Brazil"}, 33 | {"code":"IO", "name":"British lndian Ocean Territory"}, 34 | {"code":"BN", "name":"Brunei Darussalam"}, 35 | {"code":"BG", "name":"Bulgaria"}, 36 | {"code":"BF", "name":"Burkina Faso"}, 37 | {"code":"BI", "name":"Burundi"}, 38 | {"code":"KH", "name":"Cambodia"}, 39 | {"code":"CM", "name":"Cameroon"}, 40 | {"code":"CV", "name":"Cape Verde"}, 41 | {"code":"KY", "name":"Cayman Islands"}, 42 | {"code":"CF", "name":"Central African Republic"}, 43 | {"code":"TD", "name":"Chad"}, 44 | {"code":"CL", "name":"Chile"}, 45 | {"code":"CN", "name":"China"}, 46 | {"code":"CX", "name":"Christmas Island"}, 47 | {"code":"CC", "name":"Cocos (Keeling) Islands"}, 48 | {"code":"CO", "name":"Colombia"}, 49 | {"code":"KM", "name":"Comoros"}, 50 | {"code":"CG", "name":"Congo"}, 51 | {"code":"CK", "name":"Cook Islands"}, 52 | {"code":"CR", "name":"Costa Rica"}, 53 | {"code":"HR", "name":"Croatia (Hrvatska)"}, 54 | {"code":"CU", "name":"Cuba"}, 55 | {"code":"CY", "name":"Cyprus"}, 56 | {"code":"CZ", "name":"Czech Republic"}, 57 | {"code":"DK", "name":"Denmark"}, 58 | {"code":"DJ", "name":"Djibouti"}, 59 | {"code":"DM", "name":"Dominica"}, 60 | {"code":"DO", "name":"Dominican Republic"}, 61 | {"code":"TP", "name":"East Timor"}, 62 | {"code":"EC", "name":"Ecudaor"}, 63 | {"code":"EG", "name":"Egypt"}, 64 | {"code":"SV", "name":"El Salvador"}, 65 | {"code":"GQ", "name":"Equatorial Guinea"}, 66 | {"code":"ER", "name":"Eritrea"}, 67 | {"code":"EE", "name":"Estonia"}, 68 | {"code":"ET", "name":"Ethiopia"}, 69 | {"code":"FK", "name":"Falkland Islands (Malvinas)"}, 70 | {"code":"FO", "name":"Faroe Islands"}, 71 | {"code":"FJ", "name":"Fiji"}, 72 | {"code":"FI", "name":"Finland"}, 73 | {"code":"FR", "name":"France"}, 74 | {"code":"FX", "name":"France, Metropolitan"}, 75 | {"code":"GF", "name":"French Guiana"}, 76 | {"code":"PF", "name":"French Polynesia"}, 77 | {"code":"TF", "name":"French Southern Territories"}, 78 | {"code":"GA", "name":"Gabon"}, 79 | {"code":"GM", "name":"Gambia"}, 80 | {"code":"GE", "name":"Georgia"}, 81 | {"code":"DE", "name":"Germany"}, 82 | {"code":"GH", "name":"Ghana"}, 83 | {"code":"GI", "name":"Gibraltar"}, 84 | {"code":"GR", "name":"Greece"}, 85 | {"code":"GL", "name":"Greenland"}, 86 | {"code":"GD", "name":"Grenada"}, 87 | {"code":"GP", "name":"Guadeloupe"}, 88 | {"code":"GU", "name":"Guam"}, 89 | {"code":"GT", "name":"Guatemala"}, 90 | {"code":"GN", "name":"Guinea"}, 91 | {"code":"GW", "name":"Guinea-Bissau"}, 92 | {"code":"GY", "name":"Guyana"}, 93 | {"code":"HT", "name":"Haiti"}, 94 | {"code":"HM", "name":"Heard and Mc Donald Islands"}, 95 | {"code":"HN", "name":"Honduras"}, 96 | {"code":"HK", "name":"Hong Kong"}, 97 | {"code":"HU", "name":"Hungary"}, 98 | {"code":"IS", "name":"Iceland"}, 99 | {"code":"IN", "name":"India"}, 100 | {"code":"ID", "name":"Indonesia"}, 101 | {"code":"IR", "name":"Iran (Islamic Republic of)"}, 102 | {"code":"IQ", "name":"Iraq"}, 103 | {"code":"IE", "name":"Ireland"}, 104 | {"code":"IL", "name":"Israel"}, 105 | {"code":"IT", "name":"Italy"}, 106 | {"code":"CI", "name":"Ivory Coast"}, 107 | {"code":"JM", "name":"Jamaica"}, 108 | {"code":"JP", "name":"Japan"}, 109 | {"code":"JO", "name":"Jordan"}, 110 | {"code":"KZ", "name":"Kazakhstan"}, 111 | {"code":"KE", "name":"Kenya"}, 112 | {"code":"KI", "name":"Kiribati"}, 113 | {"code":"KP", "name":"Korea, Democratic People's Republic of"}, 114 | {"code":"KR", "name":"Korea, Republic of"}, 115 | {"code":"KW", "name":"Kuwait"}, 116 | {"code":"KG", "name":"Kyrgyzstan"}, 117 | {"code":"LA", "name":"Lao People's Democratic Republic"}, 118 | {"code":"LV", "name":"Latvia"}, 119 | {"code":"LB", "name":"Lebanon"}, 120 | {"code":"LS", "name":"Lesotho"}, 121 | {"code":"LR", "name":"Liberia"}, 122 | {"code":"LY", "name":"Libyan Arab Jamahiriya"}, 123 | {"code":"LI", "name":"Liechtenstein"}, 124 | {"code":"LT", "name":"Lithuania"}, 125 | {"code":"LU", "name":"Luxembourg"}, 126 | {"code":"MO", "name":"Macau"}, 127 | {"code":"MK", "name":"Macedonia"}, 128 | {"code":"MG", "name":"Madagascar"}, 129 | {"code":"MW", "name":"Malawi"}, 130 | {"code":"MY", "name":"Malaysia"}, 131 | {"code":"MV", "name":"Maldives"}, 132 | {"code":"ML", "name":"Mali"}, 133 | {"code":"MT", "name":"Malta"}, 134 | {"code":"MH", "name":"Marshall Islands"}, 135 | {"code":"MQ", "name":"Martinique"}, 136 | {"code":"MR", "name":"Mauritania"}, 137 | {"code":"MU", "name":"Mauritius"}, 138 | {"code":"TY", "name":"Mayotte"}, 139 | {"code":"MX", "name":"Mexico"}, 140 | {"code":"FM", "name":"Micronesia, Federated States of"}, 141 | {"code":"MD", "name":"Moldova, Republic of"}, 142 | {"code":"MC", "name":"Monaco"}, 143 | {"code":"MN", "name":"Mongolia"}, 144 | {"code":"MS", "name":"Montserrat"}, 145 | {"code":"MA", "name":"Morocco"}, 146 | {"code":"MZ", "name":"Mozambique"}, 147 | {"code":"MM", "name":"Myanmar"}, 148 | {"code":"NA", "name":"Namibia"}, 149 | {"code":"NR", "name":"Nauru"}, 150 | {"code":"NP", "name":"Nepal"}, 151 | {"code":"NL", "name":"Netherlands"}, 152 | {"code":"AN", "name":"Netherlands Antilles"}, 153 | {"code":"NC", "name":"New Caledonia"}, 154 | {"code":"NZ", "name":"New Zealand"}, 155 | {"code":"NI", "name":"Nicaragua"}, 156 | {"code":"NE", "name":"Niger"}, 157 | {"code":"NG", "name":"Nigeria"}, 158 | {"code":"NU", "name":"Niue"}, 159 | {"code":"NF", "name":"Norfork Island"}, 160 | {"code":"MP", "name":"Northern Mariana Islands"}, 161 | {"code":"NO", "name":"Norway"}, 162 | {"code":"OM", "name":"Oman"}, 163 | {"code":"PK", "name":"Pakistan"}, 164 | {"code":"PW", "name":"Palau"}, 165 | {"code":"PA", "name":"Panama"}, 166 | {"code":"PG", "name":"Papua New Guinea"}, 167 | {"code":"PY", "name":"Paraguay"}, 168 | {"code":"PE", "name":"Peru"}, 169 | {"code":"PH", "name":"Philippines"}, 170 | {"code":"PN", "name":"Pitcairn"}, 171 | {"code":"PL", "name":"Poland"}, 172 | {"code":"PT", "name":"Portugal"}, 173 | {"code":"PR", "name":"Puerto Rico"}, 174 | {"code":"QA", "name":"Qatar"}, 175 | {"code":"RE", "name":"Reunion"}, 176 | {"code":"RO", "name":"Romania"}, 177 | {"code":"RU", "name":"Russian Federation"}, 178 | {"code":"RW", "name":"Rwanda"}, 179 | {"code":"KN", "name":"Saint Kitts and Nevis"}, 180 | {"code":"LC", "name":"Saint Lucia"}, 181 | {"code":"VC", "name":"Saint Vincent and the Grenadines"}, 182 | {"code":"WS", "name":"Samoa"}, 183 | {"code":"SM", "name":"San Marino"}, 184 | {"code":"ST", "name":"Sao Tome and Principe"}, 185 | {"code":"SA", "name":"Saudi Arabia"}, 186 | {"code":"SN", "name":"Senegal"}, 187 | {"code":"SC", "name":"Seychelles"}, 188 | {"code":"SL", "name":"Sierra Leone"}, 189 | {"code":"SG", "name":"Singapore"}, 190 | {"code":"SK", "name":"Slovakia"}, 191 | {"code":"SI", "name":"Slovenia"}, 192 | {"code":"SB", "name":"Solomon Islands"}, 193 | {"code":"SO", "name":"Somalia"}, 194 | {"code":"ZA", "name":"South Africa"}, 195 | {"code":"GS", "name":"South Georgia South Sandwich Islands"}, 196 | {"code":"ES", "name":"Spain"}, 197 | {"code":"LK", "name":"Sri Lanka"}, 198 | {"code":"SH", "name":"St. Helena"}, 199 | {"code":"PM", "name":"St. Pierre and Miquelon"}, 200 | {"code":"SD", "name":"Sudan"}, 201 | {"code":"SR", "name":"Suriname"}, 202 | {"code":"SJ", "name":"Svalbarn and Jan Mayen Islands"}, 203 | {"code":"SZ", "name":"Swaziland"}, 204 | {"code":"SE", "name":"Sweden"}, 205 | {"code":"CH", "name":"Switzerland"}, 206 | {"code":"SY", "name":"Syrian Arab Republic"}, 207 | {"code":"TW", "name":"Taiwan"}, 208 | {"code":"TJ", "name":"Tajikistan"}, 209 | {"code":"TZ", "name":"Tanzania, United Republic of"}, 210 | {"code":"TH", "name":"Thailand"}, 211 | {"code":"TG", "name":"Togo"}, 212 | {"code":"TK", "name":"Tokelau"}, 213 | {"code":"TO", "name":"Tonga"}, 214 | {"code":"TT", "name":"Trinidad and Tobago"}, 215 | {"code":"TN", "name":"Tunisia"}, 216 | {"code":"TR", "name":"Turkey"}, 217 | {"code":"TM", "name":"Turkmenistan"}, 218 | {"code":"TC", "name":"Turks and Caicos Islands"}, 219 | {"code":"TV", "name":"Tuvalu"}, 220 | {"code":"UG", "name":"Uganda"}, 221 | {"code":"UA", "name":"Ukraine"}, 222 | {"code":"AE", "name":"United Arab Emirates"}, 223 | {"code":"GB", "name":"United Kingdom"}, 224 | {"code":"UM", "name":"United States minor outlying islands"}, 225 | {"code":"UY", "name":"Uruguay"}, 226 | {"code":"UZ", "name":"Uzbekistan"}, 227 | {"code":"VU", "name":"Vanuatu"}, 228 | {"code":"VA", "name":"Vatican City State"}, 229 | {"code":"VE", "name":"Venezuela"}, 230 | {"code":"VN", "name":"Vietnam"}, 231 | {"code":"VG", "name":"Virigan Islands (British)"}, 232 | {"code":"VI", "name":"Virgin Islands (U.S.)"}, 233 | {"code":"WF", "name":"Wallis and Futuna Islands"}, 234 | {"code":"EH", "name":"Western Sahara"}, 235 | {"code":"YE", "name":"Yemen"}, 236 | {"code":"YU", "name":"Yugoslavia"}, 237 | {"code":"ZR", "name":"Zaire"}, 238 | {"code":"ZM", "name":"Zambia"}, 239 | {"code":"ZW", "name":"Zimbabwe"}] -------------------------------------------------------------------------------- /library/Shanty/Mongo.php: -------------------------------------------------------------------------------- 1 | toArray(); 102 | } 103 | 104 | $blurbs = array('host', 'master', 'masters', 'slaves', 'slave', 'hosts'); 105 | $intersection = array_intersect(array_keys($options), $blurbs); 106 | 107 | $connectionGroups = array(); 108 | if (!empty($intersection)) $connectionGroups['default'] = $options; 109 | else $connectionGroups = $options; 110 | 111 | foreach ($connectionGroups as $connectionGroupName => $connectionGroupOptions) { 112 | static::getConnectionGroup($connectionGroupName)->addConnections($connectionGroupOptions); 113 | } 114 | } 115 | 116 | /** 117 | * Get the requirement matching the name provided 118 | * 119 | * @param $name String Name of requirement 120 | * @return mixed 121 | **/ 122 | public static function retrieveRequirement($name, $options = null) 123 | { 124 | // Requirement is already initialised return it 125 | if (isset(static::$_requirements[$name])) { 126 | // If requirement does not have options, returned cached instance 127 | if (!$options) return static::$_requirements[$name]; 128 | 129 | $requirementClass = get_class(static::$_requirements[$name]); 130 | return new $requirementClass($options); 131 | } 132 | 133 | // Attempt to create requirement 134 | if (!$requirement = static::createRequirement($name, $options)) { 135 | require_once 'Shanty/Mongo/Exception.php'; 136 | throw new Shanty_Mongo_Exception("No requirement exists for '{$name}'"); 137 | } 138 | 139 | // Requirement found. Store it for later use 140 | if (!is_null($options)) { 141 | static::storeRequirement($name, $requirement); 142 | } 143 | 144 | return $requirement; 145 | } 146 | 147 | /** 148 | * Add requirements to use in validation of document properties 149 | * 150 | * @param $name String Name of requirement 151 | * @param $requirement mixed 152 | **/ 153 | public static function storeRequirement($name, $requirement) 154 | { 155 | // Ensure $name is a string 156 | $name = (string) $name; 157 | 158 | static::$_requirements[$name] = $requirement; 159 | } 160 | 161 | /** 162 | * Add a creator of requirements 163 | * 164 | * @param String Regex to match this requirement producer 165 | * @param Closure Function to create requirement 166 | **/ 167 | public static function storeRequirementCreator($regex, Closure $function) 168 | { 169 | static::$_requirementCreators[$regex] = $function; 170 | } 171 | 172 | /** 173 | * Create a requirement 174 | * 175 | * @param $name String Name of requirement 176 | * @return mixed 177 | **/ 178 | public static function createRequirement($name, $options = null) 179 | { 180 | // Match requirement name against regex's 181 | $requirements = array_reverse(static::$_requirementCreators); 182 | foreach ($requirements as $regex => $function) { 183 | $matches = array(); 184 | preg_match($regex, $name, $matches); 185 | 186 | if (!empty($matches)) { 187 | return $function($matches, $options); 188 | } 189 | } 190 | 191 | return null; 192 | } 193 | 194 | 195 | /** 196 | * Remove all requirements 197 | */ 198 | public static function removeRequirements() 199 | { 200 | static::$_requirements = array(); 201 | } 202 | 203 | /** 204 | * Remove all requirement creators 205 | */ 206 | public static function removeRequirementCreators() 207 | { 208 | static::$_requirementCreators = array(); 209 | } 210 | 211 | /** 212 | * Deterimine if an operation is valid 213 | * 214 | * @param string $operation 215 | */ 216 | public static function isValidOperation($operation) 217 | { 218 | return in_array($operation, static::$_validOperations); 219 | } 220 | 221 | /** 222 | * Determine if a connection group exists 223 | * 224 | * @param string $name The name of the connection group 225 | */ 226 | public static function hasConnectionGroup($name) 227 | { 228 | return array_key_exists($name, static::$_connectionGroups); 229 | } 230 | 231 | /** 232 | * Set a connection group 233 | * 234 | * @param string $name 235 | * @param Shanty_Mongo_Connection_Group $connectionGroup 236 | */ 237 | public static function setConnectionGroup($name, Shanty_Mongo_Connection_Group $connectionGroup) 238 | { 239 | static::$_connectionGroups[$name] = $connectionGroup; 240 | } 241 | 242 | /** 243 | * Get a connection group. If it doesn't already exist, create it 244 | * 245 | * @param string $name The name of the connection group 246 | * @return Shanty_Mongo_Connection_Group 247 | */ 248 | public static function getConnectionGroup($name) 249 | { 250 | if (!static::hasConnectionGroup($name)) { 251 | static::setConnectionGroup($name, new Shanty_Mongo_Connection_Group()); 252 | } 253 | 254 | return static::$_connectionGroups[$name]; 255 | } 256 | 257 | /** 258 | * Get a list of all connection groups 259 | * 260 | * @return array 261 | */ 262 | public static function getConnectionGroups() 263 | { 264 | return static::$_connectionGroups; 265 | } 266 | 267 | /** 268 | * Remove all connection groups 269 | */ 270 | public static function removeConnectionGroups() 271 | { 272 | static::$_connectionGroups = array(); 273 | } 274 | 275 | 276 | /** 277 | * Add a connection to a master server 278 | * 279 | * @param Shanty_Mongo_Connection $connection 280 | * @param int $weight 281 | */ 282 | public static function addMaster(Shanty_Mongo_Connection $connection, $weight = 1, $connectionGroup = 'default') 283 | { 284 | static::getConnectionGroup($connectionGroup)->addMaster($connection, $weight); 285 | } 286 | 287 | /** 288 | * Add a connection to a slaver server 289 | * 290 | * @param $connection 291 | * @param $weight 292 | */ 293 | public static function addSlave(Shanty_Mongo_Connection $connection, $weight = 1, $connectionGroup = 'default') 294 | { 295 | static::getConnectionGroup($connectionGroup)->addSlave($connection, $weight); 296 | } 297 | 298 | /** 299 | * Get a write connection 300 | * 301 | * @param string $connectionGroupName The connection group name 302 | * @return Shanty_Mongo_Connection 303 | */ 304 | public static function getWriteConnection($connectionGroupName = 'default') 305 | { 306 | $connectionGroup = static::getConnectionGroup($connectionGroupName); 307 | 308 | if ($connectionGroupName == 'default' && count($connectionGroup->getMasters()) === 0) { 309 | // Add a connection to localhost if no connections currently exist for the default connection group 310 | $connectionGroup->addMaster(new Shanty_Mongo_Connection('127.0.0.1')); 311 | } 312 | 313 | if (!$connection = $connectionGroup->getWriteConnection($connectionGroupName)) { 314 | require_once 'Shanty/Mongo/Exception.php'; 315 | throw new Shanty_Mongo_Exception("No write connection available for the '{$connectionGroupName}' connection group"); 316 | } 317 | 318 | return $connection; 319 | } 320 | 321 | /** 322 | * Get a read connection 323 | * 324 | * @param string $connectionGroupName The connection group name 325 | * @return Shanty_Mongo_Connection 326 | */ 327 | public static function getReadConnection($connectionGroupName = 'default') 328 | { 329 | $connectionGroup = static::getConnectionGroup($connectionGroupName); 330 | 331 | if ($connectionGroupName == 'default' && count($connectionGroup->getSlaves()) === 0 && count($connectionGroup->getMasters()) === 0) { 332 | // Add a connection to localhost if no connections currently exist for the default connection group 333 | $connectionGroup->addMaster(new Shanty_Mongo_Connection('127.0.0.1')); 334 | } 335 | 336 | if (!$connection = $connectionGroup->getReadConnection($connectionGroupName)) { 337 | require_once 'Shanty/Mongo/Exception.php'; 338 | throw new Shanty_Mongo_Exception("No read connection available for the '{$connectionGroupName}' connection group"); 339 | } 340 | 341 | return $connection; 342 | } 343 | 344 | /** 345 | * Return Shanty_Mongo to pre-init status 346 | */ 347 | public static function makeClean() 348 | { 349 | static::removeConnectionGroups(); 350 | static::removeRequirements(); 351 | static::removeRequirementCreators(); 352 | static::$_initialised = false; 353 | } 354 | } 355 | 356 | Shanty_Mongo::init(); -------------------------------------------------------------------------------- /tests/Shanty/MongoTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0, count(Shanty_Mongo::getConnectionGroups())); 20 | Shanty_Mongo::setConnectionGroup('users', new Shanty_Mongo_Connection_Group()); 21 | Shanty_Mongo::makeClean(); 22 | $this->assertEquals(0, count(Shanty_Mongo::getConnectionGroups())); 23 | } 24 | 25 | public function testConnectionGroups() 26 | { 27 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_ARRAY, Shanty_Mongo::getConnectionGroups()); 28 | $this->assertEquals(0, count(Shanty_Mongo::getConnectionGroups())); 29 | 30 | $connectionGroup = new Shanty_Mongo_Connection_Group(); 31 | Shanty_Mongo::setConnectionGroup('users', $connectionGroup); 32 | $this->assertEquals(1, count(Shanty_Mongo::getConnectionGroups())); 33 | $this->assertEquals(Shanty_Mongo::getConnectionGroup('users'), $connectionGroup); 34 | 35 | $this->assertFalse(Shanty_Mongo::hasConnectionGroup('accounts')); 36 | 37 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, Shanty_Mongo::getConnectionGroup('accounts')); 38 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroups())); 39 | 40 | $this->assertTrue(Shanty_Mongo::hasConnectionGroup('accounts')); 41 | } 42 | 43 | public function testAddMaster() 44 | { 45 | $connection = new Shanty_Mongo_Connection('localhost'); 46 | Shanty_Mongo::addMaster($connection); 47 | $this->assertEquals($connection, Shanty_Mongo::getConnectionGroup('default')->getWriteConnection()); 48 | } 49 | 50 | public function testAddSlave() 51 | { 52 | $connection = new Shanty_Mongo_Connection('localhost'); 53 | Shanty_Mongo::addSlave($connection); 54 | $this->assertEquals($connection, Shanty_Mongo::getConnectionGroup('default')->getReadConnection()); 55 | } 56 | 57 | /** 58 | * @depends testAddMaster 59 | */ 60 | public function testGetWriteConnection() 61 | { 62 | $connection = Shanty_Mongo::getWriteConnection(); 63 | $this->assertNotNull($connection); 64 | $connectionInfo = $connection->getConnectionInfo(); 65 | $this->assertEquals('127.0.0.1', $connectionInfo['connectionString']); 66 | 67 | Shanty_Mongo::removeConnectionGroups(); 68 | 69 | $connection = $this->getMock('Shanty_Mongo_Connection'); 70 | Shanty_Mongo::addMaster($connection); 71 | $this->assertEquals($connection, Shanty_Mongo::getWriteConnection()); 72 | 73 | Shanty_Mongo::removeConnectionGroups(); 74 | 75 | $connection = $this->getMock('Shanty_Mongo_Connection'); 76 | Shanty_Mongo::addMaster($connection, 1, 'users'); 77 | $this->assertEquals($connection, Shanty_Mongo::getWriteConnection('users')); 78 | } 79 | 80 | /** 81 | * @depends testGetWriteConnection 82 | * @expectedException Shanty_Mongo_Exception 83 | */ 84 | public function testNoWriteConnections() 85 | { 86 | Shanty_Mongo::getWriteConnection('users'); 87 | } 88 | 89 | /** 90 | * @depends testAddSlave 91 | */ 92 | public function testGetReadConnection() 93 | { 94 | $connection = Shanty_Mongo::getReadConnection(); 95 | $this->assertNotNull($connection); 96 | $this->assertEquals($connection, Shanty_Mongo::getWriteConnection()); 97 | $connectionInfo = $connection->getConnectionInfo(); 98 | $this->assertEquals('127.0.0.1', $connectionInfo['connectionString']); 99 | 100 | Shanty_Mongo::removeConnectionGroups(); 101 | 102 | $connection = $this->getMock('Shanty_Mongo_Connection'); 103 | Shanty_Mongo::addSlave($connection); 104 | $this->assertEquals($connection, Shanty_Mongo::getReadConnection()); 105 | 106 | Shanty_Mongo::removeConnectionGroups(); 107 | 108 | $connection = $this->getMock('Shanty_Mongo_Connection'); 109 | Shanty_Mongo::addSlave($connection, 1, 'users'); 110 | $this->assertEquals($connection, Shanty_Mongo::getReadConnection('users')); 111 | } 112 | 113 | /** 114 | * @depends testGetWriteConnection 115 | * @expectedException Shanty_Mongo_Exception 116 | */ 117 | public function testNoReadConnections() 118 | { 119 | Shanty_Mongo::getReadConnection('users'); 120 | } 121 | 122 | public function testAddConnectionsDefaultGroup() 123 | { 124 | $connections = array( 125 | 'masters' => array( 126 | 0 => array('host' => '127.0.0.1'), 127 | 1 => array('host' => 'localhost') 128 | ), 129 | 'slaves' => array( 130 | 0 => array('host' => '127.0.0.1'), 131 | 1 => array('host' => 'localhost') 132 | ) 133 | ); 134 | 135 | Shanty_Mongo::addConnections($connections); 136 | $this->assertEquals(1, count(Shanty_Mongo::getConnectionGroups())); 137 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('default')->getMasters())); 138 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('default')->getSlaves())); 139 | } 140 | 141 | public function testAddConnectionsMultipleGroups() 142 | { 143 | $connections = array( 144 | 'users' => array( 145 | 'host' => 'localhost' 146 | ), 147 | 'accounts' => array( 148 | 'masters' => array( 149 | 0 => array('host' => '127.0.0.1'), 150 | 1 => array('host' => 'localhost'), 151 | ), 152 | 'slaves' => array( 153 | 0 => array('host' => '127.0.0.1'), 154 | 1 => array('host' => 'localhost') 155 | ) 156 | ) 157 | ); 158 | 159 | Shanty_Mongo::addConnections($connections); 160 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroups())); 161 | $this->assertEquals(1, count(Shanty_Mongo::getConnectionGroup('users')->getMasters())); 162 | $this->assertEquals(0, count(Shanty_Mongo::getConnectionGroup('users')->getSlaves())); 163 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('accounts')->getMasters())); 164 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('accounts')->getSlaves())); 165 | 166 | Shanty_Mongo::removeConnectionGroups(); 167 | $this->assertEquals(0, count(Shanty_Mongo::getConnectionGroups())); 168 | 169 | Shanty_Mongo::addConnections(new Zend_Config($connections)); 170 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroups())); 171 | $this->assertEquals(1, count(Shanty_Mongo::getConnectionGroup('users')->getMasters())); 172 | $this->assertEquals(0, count(Shanty_Mongo::getConnectionGroup('users')->getSlaves())); 173 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('accounts')->getMasters())); 174 | $this->assertEquals(2, count(Shanty_Mongo::getConnectionGroup('accounts')->getSlaves())); 175 | } 176 | 177 | public function testCreateRequirement() 178 | { 179 | $requirement = Shanty_Mongo::createRequirement('Validator:EmailAddress'); 180 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 181 | $this->assertEquals('Zend_Validate_EmailAddress', get_class($requirement)); 182 | 183 | $requirement = Shanty_Mongo::createRequirement('Filter:Alpha'); 184 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 185 | $this->assertEquals('Zend_Filter_Alpha', get_class($requirement)); 186 | 187 | $requirement = Shanty_Mongo::createRequirement('Validator:InArray', array('one', 'two')); 188 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 189 | $this->assertEquals('Zend_Validate_InArray', get_class($requirement)); 190 | $this->assertTrue($requirement->isValid('one')); 191 | $this->assertFalse($requirement->isValid('three')); 192 | 193 | // Make sure we get a fresh requirement with different options 194 | $requirement = Shanty_Mongo::createRequirement('Validator:InArray', array('three', 'four')); 195 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 196 | $this->assertEquals('Zend_Validate_InArray', get_class($requirement)); 197 | $this->assertTrue($requirement->isValid('three')); 198 | $this->assertFalse($requirement->isValid('one')); 199 | 200 | $this->assertNull(Shanty_Mongo::createRequirement('Non existing requirement')); 201 | } 202 | 203 | /* 204 | * @depends testCreateRequirement 205 | */ 206 | public function testRetrieveRequirements() 207 | { 208 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:MongoId'); 209 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 210 | $this->assertEquals('Shanty_Mongo_Validate_Class', get_class($requirement)); 211 | 212 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:Hostname'); 213 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 214 | $this->assertEquals('Zend_Validate_Hostname', get_class($requirement)); 215 | $this->assertTrue($requirement->isValid('google.com')); 216 | 217 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:Hostname', Zend_Validate_Hostname::ALLOW_IP); 218 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 219 | $this->assertEquals('Zend_Validate_Hostname', get_class($requirement)); 220 | $this->assertFalse($requirement->isValid('shantymongo.org')); 221 | 222 | $requirement = Shanty_Mongo::retrieveRequirement('Document:My_ShantyMongo_User'); 223 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 224 | $this->assertEquals('Shanty_Mongo_Validate_Class', get_class($requirement)); 225 | $user = $this->getMock('My_ShantyMongo_User'); 226 | $this->assertTrue($requirement->isValid($user)); 227 | 228 | $this->assertNull(Shanty_Mongo::createRequirement('Document:Class does not exist')); 229 | 230 | // even though we tested this with testCreateRequirement, we need to make sure the vars were passed through correctly 231 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:InArray', array('one', 'two')); 232 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 233 | $this->assertEquals('Zend_Validate_InArray', get_class($requirement)); 234 | $this->assertTrue($requirement->isValid('one')); 235 | $this->assertFalse($requirement->isValid('three')); 236 | } 237 | 238 | /** 239 | * @expectedException Shanty_Mongo_Exception 240 | */ 241 | public function testRetrieveRequirementsException() 242 | { 243 | Shanty_Mongo::retrieveRequirement('Non existant requirement'); 244 | } 245 | 246 | /** 247 | * @depends testRetrieveRequirements 248 | */ 249 | public function testStoreRequirement() 250 | { 251 | $requirement = new Zend_Validate_Hostname(); 252 | Shanty_Mongo::storeRequirement('Validator:Hostname', $requirement); 253 | 254 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:Hostname'); 255 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 256 | $this->assertEquals('Zend_Validate_Hostname', get_class($requirement)); 257 | 258 | // test requirements with options after the same requirement has been stored without options 259 | $requirement = Shanty_Mongo::retrieveRequirement('Validator:Hostname', Zend_Validate_Hostname::ALLOW_IP); 260 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 261 | $this->assertEquals('Zend_Validate_Hostname', get_class($requirement)); 262 | $this->assertFalse($requirement->isValid('shantymongo.org')); 263 | } 264 | 265 | public function testCustomValidator() 266 | { 267 | $requirement = Shanty_Mongo::createRequirement('Validator:Zend_Validate_EmailAddress'); 268 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 269 | $this->assertEquals('Zend_Validate_EmailAddress', get_class($requirement)); 270 | } 271 | 272 | public function testCustomFilter() 273 | { 274 | $requirement = Shanty_Mongo::createRequirement('Filter:Zend_Filter_Alpha'); 275 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $requirement); 276 | $this->assertEquals('Zend_Filter_Alpha', get_class($requirement)); 277 | } 278 | 279 | public static function validOperations() 280 | { 281 | return array( 282 | array('$set'), 283 | array('$unset'), 284 | array('$push'), 285 | array('$pushAll'), 286 | array('$pull'), 287 | array('$pullAll'), 288 | array('$inc') 289 | ); 290 | } 291 | 292 | /** 293 | * @dataProvider validOperations 294 | */ 295 | public function testValidOperation($operation) 296 | { 297 | $this->assertTrue(Shanty_Mongo::isValidOperation($operation)); 298 | $this->assertFalse(Shanty_Mongo::isValidOperation('non valid op')); 299 | } 300 | } -------------------------------------------------------------------------------- /library/Shanty/Mongo/Collection.php: -------------------------------------------------------------------------------- 1 | getDatabase(); 36 | } 37 | 38 | return $db; 39 | } 40 | 41 | /** 42 | * Get the name of the mongo collection 43 | * 44 | * @return string 45 | */ 46 | public static function getCollectionName() 47 | { 48 | return static::$_collection; 49 | } 50 | 51 | /** 52 | * Get the name of the connection group 53 | * 54 | * @return string 55 | */ 56 | public static function getConnectionGroupName() 57 | { 58 | return static::$_connectionGroup; 59 | } 60 | 61 | /** 62 | * Determine if this collection has a database name set 63 | * 64 | * @return boolean 65 | */ 66 | public static function hasDbName() 67 | { 68 | return !is_null(static::getDbName()); 69 | } 70 | 71 | /** 72 | * Determine if this collection has a collection name set 73 | * 74 | * @return boolean 75 | */ 76 | public static function hasCollectionName() 77 | { 78 | return !is_null(static::getCollectionName()); 79 | } 80 | 81 | /** 82 | * Is this class a document class 83 | * 84 | * @return boolean 85 | */ 86 | public static function isDocumentClass() 87 | { 88 | return is_subclass_of(get_called_class(), 'Shanty_Mongo_Document'); 89 | } 90 | 91 | /** 92 | * Get the name of the document class 93 | * 94 | * @return string 95 | */ 96 | public static function getDocumentClass() 97 | { 98 | if (!static::isDocumentClass()) { 99 | throw new Shanty_Mongo_Exception(get_called_class().' is not a document. Please extend Shanty_Mongo_Document'); 100 | } 101 | 102 | return get_called_class(); 103 | } 104 | 105 | /** 106 | * Get the name of the document set class 107 | * 108 | * @return string 109 | */ 110 | public static function getDocumentSetClass() 111 | { 112 | return static::$_documentSetClass; 113 | } 114 | 115 | /** 116 | * Get the inheritance of this collection 117 | */ 118 | public static function getCollectionInheritance() 119 | { 120 | $calledClass = get_called_class(); 121 | 122 | // Have we already computed this collections inheritance? 123 | if (array_key_exists($calledClass, static::$_cachedCollectionInheritance)) { 124 | return static::$_cachedCollectionInheritance[$calledClass]; 125 | } 126 | 127 | $parentClass = get_parent_class($calledClass); 128 | 129 | if (is_null($parentClass::getCollectionName())) { 130 | $inheritance = array($calledClass); 131 | } 132 | else { 133 | $inheritance = $parentClass::getCollectionInheritance(); 134 | array_unshift($inheritance, $calledClass); 135 | } 136 | 137 | static::$_cachedCollectionInheritance[$calledClass] = $inheritance; 138 | return $inheritance; 139 | } 140 | 141 | /** 142 | * Get requirements 143 | * 144 | * @param bolean $inherited Include inherited requirements 145 | * @return array 146 | */ 147 | public static function getCollectionRequirements($inherited = true) 148 | { 149 | $calledClass = get_called_class(); 150 | 151 | // Return if we only need direct requirements. ie no inherited requirements 152 | if (!$inherited || $calledClass === __CLASS__) { 153 | $reflector = new ReflectionProperty($calledClass, '_requirements'); 154 | if ($reflector->getDeclaringClass()->getName() !== $calledClass) return array(); 155 | 156 | return static::makeRequirementsTidy($calledClass::$_requirements); 157 | } 158 | 159 | // Have we already computed this collections requirements? 160 | if (array_key_exists($calledClass, self::$_cachedCollectionRequirements)) { 161 | return self::$_cachedCollectionRequirements[$calledClass]; 162 | } 163 | 164 | // Get parent collections requirements 165 | $parentClass = get_parent_class($calledClass); 166 | $parentRequirements = $parentClass::getCollectionRequirements(); 167 | 168 | // Merge those requirements with this collections requirements 169 | $requirements = static::mergeRequirements($parentRequirements, $calledClass::getCollectionRequirements(false)); 170 | self::$_cachedCollectionRequirements[$calledClass] = $requirements; 171 | 172 | return $requirements; 173 | } 174 | 175 | /** 176 | * Process requirements to make sure they are in the correct format 177 | * 178 | * @param array $requirements 179 | * @return array 180 | */ 181 | public static function makeRequirementsTidy(array $requirements) { 182 | foreach ($requirements as $property => $requirementList) { 183 | if (!is_array($requirementList)) { 184 | $requirements[$property] = array($requirementList); 185 | } 186 | 187 | $newRequirementList = array(); 188 | foreach ($requirements[$property] as $key => $requirement) { 189 | if (is_numeric($key)) $newRequirementList[$requirement] = null; 190 | else $newRequirementList[$key] = $requirement; 191 | } 192 | 193 | $requirements[$property] = $newRequirementList; 194 | } 195 | 196 | return $requirements; 197 | } 198 | 199 | /** 200 | * Merge a two sets of requirements together 201 | * 202 | * @param array $requirements 203 | * @return array 204 | */ 205 | public static function mergeRequirements($requirements1, $requirements2) 206 | { 207 | $requirements = $requirements1; 208 | 209 | foreach ($requirements2 as $property => $requirementList) { 210 | if (!array_key_exists($property, $requirements)) { 211 | $requirements[$property] = $requirementList; 212 | continue; 213 | } 214 | 215 | foreach ($requirementList as $requirement => $options) { 216 | // Find out if this is a Document or DocumentSet requirement 217 | $matches = array(); 218 | preg_match("/^(Document|DocumentSet)(?::[A-Za-z][\w\-]*)?$/", $requirement, $matches); 219 | 220 | if (empty($matches)) { 221 | $requirements[$property][$requirement] = $options; 222 | continue; 223 | } 224 | 225 | // If requirement exists in existing requirements then unset it and replace it with the new requirements 226 | foreach ($requirements[$property] as $innerRequirement => $innerOptions) { 227 | $innerMatches = array(); 228 | 229 | preg_match("/^{$matches[1]}(:[A-Za-z][\w\-]*)?/", $innerRequirement, $innerMatches); 230 | 231 | if (empty($innerMatches)) { 232 | continue; 233 | } 234 | 235 | unset($requirements[$property][$innerRequirement]); 236 | $requirements[$property][$requirement] = $options; 237 | break; 238 | } 239 | } 240 | } 241 | 242 | return $requirements; 243 | } 244 | 245 | /* 246 | * Get a connection 247 | * 248 | * @param $writable should the connection be writable 249 | * @return Shanty_Mongo_Connection 250 | */ 251 | public static function getConnection($writable = true) 252 | { 253 | if ($writable) $connection = Shanty_Mongo::getWriteConnection(static::getConnectionGroupName()); 254 | else $connection = Shanty_Mongo::getReadConnection(static::getConnectionGroupName()); 255 | 256 | return $connection; 257 | } 258 | 259 | /** 260 | * Get an instance of MongoDb 261 | * 262 | * @return MongoDb 263 | * @param boolean $useSlave 264 | */ 265 | public static function getMongoDb($writable = true) 266 | { 267 | if (!static::hasDbName()) { 268 | require_once 'Shanty/Mongo/Exception.php'; 269 | throw new Shanty_Mongo_Exception(get_called_class().'::$_db is null'); 270 | } 271 | 272 | return static::getConnection($writable)->selectDB(static::getDbName()); 273 | } 274 | 275 | /** 276 | * Get an instance of MongoCollection 277 | * 278 | * @return MongoCollection 279 | * @param boolean $useSlave 280 | */ 281 | public static function getMongoCollection($writable = true) 282 | { 283 | if (!static::hasCollectionName()) { 284 | throw new Shanty_Mongo_Exception(get_called_class().'::$_collection is null'); 285 | } 286 | 287 | return static::getMongoDb($writable)->selectCollection(static::getCollectionName()); 288 | } 289 | 290 | /** 291 | * Create a new document belonging to this collection 292 | * @param $data 293 | * @param boolean $new 294 | */ 295 | public static function create(array $data = array(), $new = true) 296 | { 297 | if (isset($data['_type']) && is_array($data['_type']) && class_exists($data['_type'][0]) && is_subclass_of($data['_type'][0], 'Shanty_Mongo_Document')) { 298 | $documentClass = $data['_type'][0]; 299 | } 300 | else { 301 | $documentClass = static::getDocumentClass(); 302 | } 303 | 304 | $config = array(); 305 | $config['new'] = ($new); 306 | $config['hasId'] = true; 307 | $config['connectionGroup'] = static::getConnectionGroupName(); 308 | $config['db'] = static::getDbName(); 309 | $config['collection'] = static::getCollectionName(); 310 | return new $documentClass($data, $config); 311 | } 312 | 313 | /** 314 | * Find a document by id 315 | * 316 | * @param MongoId|String $id 317 | * @param array $fields 318 | * @return Shanty_Mongo_Document 319 | */ 320 | public static function find($id, array $fields = array()) 321 | { 322 | if (!($id instanceof MongoId)) { 323 | $id = new MongoId($id); 324 | } 325 | 326 | $query = array('_id' => $id); 327 | 328 | return static::one($query, $fields); 329 | } 330 | 331 | /** 332 | * Find one document 333 | * 334 | * @param array $query 335 | * @param array $fields 336 | * @return Shanty_Mongo_Document 337 | */ 338 | public static function one(array $query = array(), array $fields = array()) 339 | { 340 | $inheritance = static::getCollectionInheritance(); 341 | if (count($inheritance) > 1) { 342 | $query['_type'] = $inheritance[0]; 343 | } 344 | 345 | // If we are selecting specific fields make sure _type is always there 346 | if (!empty($fields) && !isset($fields['_type'])) { 347 | $fields['_type'] = 1; 348 | } 349 | 350 | $data = static::getMongoCollection(false)->findOne($query, $fields); 351 | 352 | if (is_null($data)) return null; 353 | 354 | return static::create($data, false); 355 | } 356 | 357 | /** 358 | * Find many documents 359 | * 360 | * @param array $query 361 | * @param array $fields 362 | * @return Shanty_Mongo_Iterator_Cursor 363 | */ 364 | public static function all(array $query = array(), array $fields = array()) 365 | { 366 | $inheritance = static::getCollectionInheritance(); 367 | if (count($inheritance) > 1) { 368 | $query['_type'] = $inheritance[0]; 369 | } 370 | 371 | // If we are selecting specific fields make sure _type is always there 372 | if (!empty($fields) && !isset($fields['_type'])) { 373 | $fields['_type'] = 1; 374 | } 375 | 376 | $cursor = static::getMongoCollection(false)->find($query, $fields); 377 | 378 | $config = array(); 379 | $config['connectionGroup'] = static::getConnectionGroupName(); 380 | $config['db'] = static::getDbName(); 381 | $config['collection'] = static::getCollectionName(); 382 | $config['documentClass'] = static::getDocumentClass(); 383 | $config['documentSetClass'] = static::getDocumentSetClass(); 384 | 385 | return new Shanty_Mongo_Iterator_Cursor($cursor, $config); 386 | } 387 | 388 | /** 389 | * Alias for one 390 | * 391 | * @param array $query 392 | * @param array $fields 393 | * @return Shanty_Mongo_Document 394 | */ 395 | public static function fetchOne($query = array(), array $fields = array()) 396 | { 397 | return static::one($query, $fields); 398 | } 399 | 400 | /** 401 | * Alias for all 402 | * 403 | * @param array $query 404 | * @param array $fields 405 | * @return Shanty_Mongo_Iterator_Cursor 406 | */ 407 | public static function fetchAll($query = array(), array $fields = array()) 408 | { 409 | return static::all($query, $fields); 410 | } 411 | 412 | /** 413 | * Select distinct values for a property 414 | * 415 | * @param String $property 416 | * @return array 417 | */ 418 | public static function distinct($property, $query = array()) 419 | { 420 | $results = static::getMongoDb(false)->command(array('distinct' => static::getCollectionName(), 'key' => $property, 'query' => $query)); 421 | 422 | return $results['values']; 423 | } 424 | 425 | /** 426 | * Insert a document 427 | * 428 | * @param array $document 429 | * @param array $options 430 | */ 431 | public static function insert(array $document, array $options = array()) 432 | { 433 | return static::getMongoCollection(true)->insert($document, $options); 434 | } 435 | 436 | /** 437 | * Insert a batch of documents 438 | * 439 | * @param array $documents 440 | * @param unknown_type $options 441 | */ 442 | public static function insertBatch(array $documents, array $options = array()) 443 | { 444 | return static::getMongoCollection(true)->batchInsert($documents, $options); 445 | } 446 | 447 | /** 448 | * Update documents from this collection 449 | * 450 | * @param $criteria 451 | * @param $object 452 | * @param $options 453 | */ 454 | public static function update(array $criteria, array $object, array $options = array()) 455 | { 456 | return static::getMongoCollection(true)->update($criteria, $object, $options); 457 | } 458 | 459 | /** 460 | * Remove documents from this collection 461 | * 462 | * @param array $criteria 463 | * @param unknown_type $justone 464 | */ 465 | public static function remove(array $criteria, array $options = array()) 466 | { 467 | // if you want to remove a document by MongoId 468 | if (array_key_exists('_id', $criteria) && !($criteria["_id"] instanceof MongoId)) { 469 | $criteria["_id"] = new MongoId($criteria["_id"]); 470 | } 471 | 472 | return static::getMongoCollection(true)->remove($criteria, $options); 473 | } 474 | 475 | /** 476 | * Drop this collection 477 | */ 478 | public static function drop() 479 | { 480 | return static::getMongoCollection(true)->drop(); 481 | } 482 | 483 | /** 484 | * Ensure an index 485 | * 486 | * @param array $keys 487 | * @param array $options 488 | */ 489 | public static function ensureIndex(array $keys, $options = array()) 490 | { 491 | return static::getMongoCollection(true)->ensureIndex($keys, $options); 492 | } 493 | 494 | /** 495 | * Delete an index 496 | * 497 | * @param string|array $keys 498 | */ 499 | public static function deleteIndex($keys) 500 | { 501 | return static::getMongoCollection(true)->deleteIndex($keys); 502 | } 503 | 504 | /** 505 | * Remove all indexes from this collection 506 | */ 507 | public static function deleteIndexes() 508 | { 509 | return static::getMongoCollection(true)->deleteIndexes(); 510 | } 511 | 512 | /** 513 | * Get index information for this collection 514 | * 515 | * @return array 516 | */ 517 | public static function getIndexInfo() 518 | { 519 | return static::getMongoCollection(false)->getIndexInfo(); 520 | } 521 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/DocumentSetTest.php: -------------------------------------------------------------------------------- 1 | _bob = My_ShantyMongo_User::find('4c04516a1f5f5e21361e3ab0'); 14 | $this->_article = My_ShantyMongo_Article::find('4c04516f1f5f5e21361e3ac1'); 15 | } 16 | 17 | public function testGetPropertyKeys() 18 | { 19 | $this->assertEquals(array(0, 1), $this->_bob->addresses->getPropertyKeys()); 20 | $this->_bob->addresses[0] = null; 21 | 22 | $address = new Shanty_Mongo_Document(); 23 | $address->street = '16 Park Rd'; 24 | $this->_bob->addresses[] = $address; 25 | 26 | $this->assertEquals(array(1, 2), $this->_bob->addresses->getPropertyKeys()); 27 | } 28 | 29 | public function testGetProperty() 30 | { 31 | // Make sure the DocumentSet is sound 32 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_bob->addresses); 33 | $this->assertEquals('Shanty_Mongo_DocumentSet', get_class($this->_bob->addresses)); 34 | $this->assertEquals(2, count($this->_bob->addresses)); 35 | 36 | // Test basic get 37 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_bob->addresses[0]); 38 | $this->assertEquals('Shanty_Mongo_Document', get_class($this->_bob->addresses[0])); 39 | $this->assertEquals('19 Hill St', $this->_bob->addresses[0]->street); 40 | $this->assertEquals('default', $this->_bob->addresses[0]->getConfigAttribute('connectionGroup')); 41 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $this->_bob->addresses[0]->getConfigAttribute('db')); 42 | $this->assertEquals('user', $this->_bob->addresses[0]->getConfigAttribute('collection')); 43 | $this->assertEquals('addresses.0', $this->_bob->addresses[0]->getPathToDocument()); 44 | 45 | $criteria = $this->_bob->addresses[0]->getCriteria(); 46 | $this->assertTrue(isset($criteria['_id'])); 47 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $criteria['_id']->__toString()); 48 | 49 | $this->assertFalse($this->_bob->addresses[0]->isNewDocument()); 50 | $this->assertTrue($this->_bob->addresses[0]->getConfigAttribute('parentIsDocumentSet')); 51 | 52 | // Test non existing index 53 | $this->assertNull($this->_bob->addresses[404]); 54 | 55 | // Test known references 56 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_bob->friends[1]); 57 | $this->assertEquals('My_ShantyMongo_ArtStudent', get_class($this->_bob->friends[1])); 58 | 59 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_article->contributors); 60 | $this->assertEquals('My_ShantyMongo_Users', get_class($this->_article->contributors)); 61 | $this->assertEquals(2, count($this->_article->contributors)); 62 | 63 | $user = $this->_article->contributors[0]; 64 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $user); 65 | $this->assertEquals('My_ShantyMongo_Student', get_class($user)); 66 | $this->assertEquals('Cherry Jones', $user->name->full()); 67 | 68 | $this->assertEquals('default', $user->getConfigAttribute('connectionGroup')); 69 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $user->getConfigAttribute('db')); 70 | $this->assertEquals('user', $user->getConfigAttribute('collection')); 71 | $this->assertEquals('', $user->getPathToDocument()); 72 | 73 | $criteria = $user->getCriteria(); 74 | $this->assertTrue(isset($criteria['_id'])); 75 | $this->assertEquals('4c04516f1f5f5e21361e3ab1', $criteria['_id']->__toString()); 76 | 77 | // Test broken reference 78 | $this->assertNull($this->_bob->friends[2]); 79 | 80 | // Test unknown references 81 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_article->relatedArticles); 82 | $this->assertEquals('Shanty_Mongo_DocumentSet', get_class($this->_article->relatedArticles)); 83 | $this->assertEquals(1, count($this->_article->relatedArticles)); 84 | 85 | $article = $this->_article->relatedArticles[0]; 86 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $article); 87 | $this->assertEquals('My_ShantyMongo_Article', get_class($article)); 88 | $this->assertEquals('How to use Bend Space and Time', $article->title); 89 | } 90 | 91 | public function testGetPropertyNewDocument() 92 | { 93 | $address = $this->_bob->addresses->new(); 94 | $address->street = '45 Burrow St'; 95 | $address->suburb = 'The Shire'; 96 | $address->state = 'Middle Earth'; 97 | $address->postcode = '21342'; 98 | 99 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $address); 100 | $this->assertEquals('Shanty_Mongo_Document', get_class($address)); 101 | $this->assertTrue($address->isNewDocument()); 102 | $this->assertFalse($address->hasId()); 103 | $this->assertTrue($address->getConfigAttribute('parentIsDocumentSet')); 104 | 105 | $this->assertEquals('default', $address->getConfigAttribute('connectionGroup')); 106 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $address->getConfigAttribute('db')); 107 | $this->assertEquals('user', $address->getConfigAttribute('collection')); 108 | $this->assertEquals('addresses', $address->getPathToDocument()); 109 | 110 | $criteria = $address->getCriteria(); 111 | $this->assertTrue(isset($criteria['_id'])); 112 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $criteria['_id']->__toString()); 113 | 114 | $address->save(); 115 | 116 | $bob = My_ShantyMongo_User::find('4c04516a1f5f5e21361e3ab0'); 117 | $this->assertEquals(3, count($bob->addresses)); 118 | $this->assertEquals('45 Burrow St', $bob->addresses[2]->street); 119 | } 120 | 121 | /** 122 | * @expectedException Shanty_Mongo_Exception 123 | */ 124 | public function testGetPropertyNewDocumentException() 125 | { 126 | $user = $this->_article->contributors->new(); 127 | } 128 | 129 | public function testSetProperty() 130 | { 131 | // Unset and existing document 132 | $this->_bob->addresses[0] = null; 133 | $this->assertEquals(null, $this->_bob->addresses[0]); 134 | 135 | // Test adding new documents into document set 136 | $address = new Shanty_Mongo_Document(); 137 | $address->street = '16 Park Rd'; 138 | $objStorage = new SplObjectStorage(); 139 | $objStorage->attach($address); 140 | $this->_bob->addresses[2] = $address; 141 | $this->assertTrue($objStorage->contains($this->_bob->addresses[2])); 142 | $this->assertEquals('default', $this->_bob->addresses[2]->getConfigAttribute('connectionGroup')); 143 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $this->_bob->addresses[2]->getConfigAttribute('db')); 144 | $this->assertEquals('user', $this->_bob->addresses[2]->getConfigAttribute('collection')); 145 | $this->assertEquals('addresses.2', $this->_bob->addresses[2]->getPathToDocument()); 146 | 147 | $requirements = array( 148 | '_id' => array('Validator:MongoId' => null), 149 | '_type' => array('Array' => null), 150 | 'street' => array('Required' => null), 151 | 'state' => array('Required' => null), 152 | 'suburb' => array('Required' => null), 153 | 'postcode' => array('Required' => null), 154 | ); 155 | 156 | $this->assertEquals($requirements, $this->_bob->addresses[2]->getRequirements()); 157 | 158 | $this->assertEquals(array(1, 2), $this->_bob->addresses->getPropertyKeys()); 159 | 160 | // Test adding documents into an unknown index 161 | $address = new Shanty_Mongo_Document(); 162 | $address->street = 'testing'; 163 | $objStorage = new SplObjectStorage(); 164 | $objStorage->attach($address); 165 | $this->_bob->addresses[] = $address; 166 | $this->assertTrue($objStorage->contains($this->_bob->addresses[3])); 167 | $this->assertEquals('default', $this->_bob->addresses[3]->getConfigAttribute('connectionGroup')); 168 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $this->_bob->addresses[3]->getConfigAttribute('db')); 169 | $this->assertEquals('user', $this->_bob->addresses[3]->getConfigAttribute('collection')); 170 | $this->assertEquals('addresses.3', $this->_bob->addresses[3]->getPathToDocument()); 171 | 172 | $criteria = $this->_bob->addresses[3]->getCriteria(); 173 | $this->assertTrue(isset($criteria['_id'])); 174 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $criteria['_id']->__toString()); 175 | 176 | // Test adding old documents to document set 177 | $objStorage = new SplObjectStorage(); 178 | $objStorage->attach($this->_bob->addresses[1]); 179 | $this->_bob->addresses[4] = $this->_bob->addresses[1]; 180 | $this->assertFalse($objStorage->contains($this->_bob->addresses[4])); 181 | $this->assertEquals('addresses.4', $this->_bob->addresses[4]->getPathToDocument()); 182 | $this->assertFalse($this->_bob->addresses[4]->isNewDocument()); 183 | $this->assertEquals('Springfield', $this->_bob->addresses[4]->suburb); 184 | 185 | // Test adding documents that will be saved as references 186 | $objStorage = new SplObjectStorage(); 187 | $objStorage->attach($this->_bob->friends[0]); 188 | $this->_article->contributors[5] = $this->_bob->friends[0]; 189 | $this->assertFalse($this->_article->contributors[5]->isNewDocument()); 190 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_article->contributors[5]); 191 | $this->assertEquals('My_ShantyMongo_Student', get_class($this->_article->contributors[5])); 192 | $this->assertTrue($objStorage->contains($this->_article->contributors[5])); 193 | $this->assertEquals('default', $this->_article->contributors[5]->getConfigAttribute('connectionGroup')); 194 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, $this->_article->contributors[5]->getConfigAttribute('db')); 195 | $this->assertEquals('user', $this->_article->contributors[5]->getConfigAttribute('collection')); 196 | $this->assertEquals('', $this->_article->contributors[5]->getPathToDocument()); 197 | 198 | $requirements = array( 199 | '_id' => array('Validator:MongoId' => null), 200 | '_type' => array('Array' => null), 201 | 'name' => array('Document:My_ShantyMongo_Name' => null, 'Required' => null), 202 | 'concession' => array('Required' => null), 203 | 'email' => array('Required' => null, 'Validator:EmailAddress' => null), 204 | 'addresses' => array('DocumentSet' => null), 205 | 'addresses.$.street' => array('Required' => null), 206 | 'addresses.$.state' => array('Required' => null), 207 | 'addresses.$.suburb' => array('Required' => null), 208 | 'addresses.$.postcode' => array('Required' => null), 209 | 'friends' => array('DocumentSet:My_ShantyMongo_Users' => null), 210 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 211 | 'sex' => array('Required' => null, 'Validator:InArray' => array('F', 'M')), 212 | 'partner' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null) 213 | ); 214 | 215 | $this->assertEquals($requirements, $this->_article->contributors[5]->getRequirements()); 216 | 217 | $criteria = $this->_article->contributors[5]->getCriteria(); 218 | $this->assertTrue(isset($criteria['_id'])); 219 | $this->assertEquals('4c04516f1f5f5e21361e3ab1', $criteria['_id']->__toString()); 220 | 221 | // Test displacing an unknown reference 222 | $article = $this->_article->relatedArticles[0]; 223 | $this->assertTrue($this->_article->relatedArticles->isReference($article)); 224 | 225 | $newArticle = new My_ShantyMongo_Article(); 226 | $newArticle->title = 'Boo'; 227 | $this->_article->relatedArticles[0] = $article; 228 | $this->assertFalse($this->_article->relatedArticles->isReference($article)); 229 | } 230 | 231 | /** 232 | * @expectedException Shanty_Mongo_Exception 233 | */ 234 | public function testSetPropertyNonNumericIndexException() 235 | { 236 | $this->_bob->addresses['non numeric index'] = new Shanty_Mongo_Document(); 237 | } 238 | 239 | /** 240 | * @expectedException Shanty_Mongo_Exception 241 | */ 242 | public function testSetPropertyNonDocumentException() 243 | { 244 | $this->_bob->addresses[5] = 'Not a document'; 245 | } 246 | 247 | /** 248 | * @expectedException Shanty_Mongo_Exception 249 | */ 250 | public function testSetPropertyRequirementsException() 251 | { 252 | $address = new Shanty_Mongo_Document(); 253 | $address->street = '234 '; 254 | $this->_bob->addresses[] = $address; 255 | $address->export(); 256 | } 257 | 258 | public function testExport() 259 | { 260 | $this->_bob->addresses[0] = null; 261 | 262 | $address = new Shanty_Mongo_Document(); 263 | $address->street = '155 Long St'; 264 | $address->suburb = 'Big Place'; 265 | $address->state = 'QLD'; 266 | $address->postcode = '4000'; 267 | $address->country = 'Australia'; 268 | 269 | $this->_bob->addresses[] = $address; 270 | 271 | $exportData = array( 272 | null, 273 | array( 274 | 'street' => '742 Evergreen Terrace', 275 | 'suburb' => 'Springfield', 276 | 'state' => 'Nevada', 277 | 'postcode' => '89002', 278 | 'country' => 'USA' 279 | ), 280 | array( 281 | 'street' => '155 Long St', 282 | 'suburb' => 'Big Place', 283 | 'state' => 'QLD', 284 | 'postcode' => '4000', 285 | 'country' => 'Australia' 286 | ) 287 | ); 288 | 289 | $this->assertEquals($exportData, $this->_bob->addresses->export()); 290 | } 291 | 292 | public function testExportReferences() 293 | { 294 | // Pull all reference doc 295 | foreach ($this->_bob->friends as $friend) { 296 | 297 | } 298 | 299 | // Bob is going to become a friend of himself 300 | $this->_bob->friends[] = $this->_bob; 301 | 302 | $exportData = $this->_bob->friends->export(); 303 | $this->assertEquals(3, count($exportData)); 304 | 305 | $this->assertTrue(MongoDBRef::isRef($exportData[0])); 306 | $this->assertEquals('4c04516f1f5f5e21361e3ab1', $exportData[0]['$id']->__toString()); 307 | 308 | $this->assertTrue(MongoDBRef::isRef($exportData[1])); 309 | $this->assertEquals('4c0451791f5f5e21361e3ab2', $exportData[1]['$id']->__toString()); 310 | 311 | $this->assertTrue(MongoDBRef::isRef($exportData[2])); 312 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $exportData[2]['$id']->__toString()); 313 | } 314 | 315 | public function testAddDocument() 316 | { 317 | $address = new Shanty_Mongo_Document(); 318 | $address->street = '155 Long St'; 319 | $address->suburb = 'Big Place'; 320 | $address->state = 'QLD'; 321 | $address->postcode = '4000'; 322 | $address->country = 'Australia'; 323 | 324 | $objStorage = new SplObjectStorage(); 325 | $objStorage->attach($address); 326 | $this->_bob->addresses->addDocument($address); 327 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $this->_bob->addresses[2]); 328 | $this->assertEquals('Shanty_Mongo_Document', get_class($this->_bob->addresses[2])); 329 | $this->assertTrue($objStorage->contains($this->_bob->addresses[2])); 330 | } 331 | 332 | public function testPushDocument() 333 | { 334 | $address1 = new Shanty_Mongo_Document(); 335 | $address1->street = '155 Long St'; 336 | $address1->suburb = 'Big Place'; 337 | $address1->state = 'QLD'; 338 | $address1->postcode = '4000'; 339 | $address1->country = 'Australia'; 340 | 341 | $this->_bob->addresses->pushDocument($address1); 342 | 343 | $address2 = new Shanty_Mongo_Document(); 344 | $address2->street = '2 Short St'; 345 | $address2->suburb = 'Big Place'; 346 | $address2->state = 'QLD'; 347 | $address2->postcode = '4000'; 348 | $address2->country = 'Australia'; 349 | 350 | $this->_bob->addresses->pushDocument($address2); 351 | 352 | $operations = array( 353 | '$pushAll' => array( 354 | 'addresses' => array( 355 | $address1->export(), 356 | $address2->export() 357 | ) 358 | ) 359 | ); 360 | 361 | $this->assertEquals($operations, $this->_bob->addresses->getOperations()); 362 | } 363 | 364 | public function testGetOperations() 365 | { 366 | // Test non references 367 | $this->_bob->addresses[0]->addOperation('$set', 'street', '43 Hole St'); 368 | $this->_bob->addresses[0]->addOperation('$set', 'suburb', 'Ipswich'); 369 | $this->_bob->addresses[1]->addOperation('$set', 'street', '745 Evergreen Terrace'); 370 | 371 | $operations = array( 372 | '$set' => array( 373 | 'addresses.0.street' => '43 Hole St', 374 | 'addresses.0.suburb' => 'Ipswich', 375 | 'addresses.1.street' => '745 Evergreen Terrace', 376 | ) 377 | ); 378 | 379 | $this->assertEquals($operations, $this->_bob->addresses->getOperations(true)); 380 | 381 | // Test references 382 | $this->_article->contributors[0]->addOperation('$set', 'email', 'blabla@domain.com'); 383 | $this->assertEquals(array(), $this->_article->contributors->getOperations(true)); 384 | } 385 | 386 | public function testPurgeOperations() 387 | { 388 | $this->_bob->addresses[0]->addOperation('$set', 'street', '43 Hole St'); 389 | $this->_bob->addresses[0]->addOperation('$set', 'suburb', 'Ipswich'); 390 | $this->_bob->addresses[1]->addOperation('$set', 'street', '745 Evergreen Terrace'); 391 | $this->_bob->addresses->purgeOperations(true); 392 | $this->assertEquals(array(), $this->_bob->addresses->getOperations(true)); 393 | 394 | // Test references 395 | $this->_article->contributors[0]->addOperation('$set', 'email', 'blabla@domain.com'); 396 | $this->_article->contributors->purgeOperations(true); 397 | 398 | $operations = array( 399 | '$set' => array( 400 | 'email' => 'blabla@domain.com', 401 | ) 402 | ); 403 | 404 | $this->assertEquals($operations, $this->_article->contributors[0]->getOperations(true)); 405 | } 406 | 407 | public function testMagicCall() 408 | { 409 | // Test get new document 410 | $newAddress = $this->_bob->addresses->new(); 411 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $newAddress); 412 | $this->assertEquals('Shanty_Mongo_Document', get_class($newAddress)); 413 | } 414 | 415 | /** 416 | * @expectedException Shanty_Mongo_Exception 417 | */ 418 | public function testMagicCallException() 419 | { 420 | $this->_bob->addresses->noMethod(); 421 | } 422 | } -------------------------------------------------------------------------------- /tests/Shanty/Mongo/CollectionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(TESTS_SHANTY_MONGO_DB, My_ShantyMongo_User::getDbName()); 16 | 17 | Shanty_Mongo::removeConnectionGroups(); 18 | 19 | $connection = new Shanty_Mongo_Connection('localhost/shanty-mongo'); 20 | Shanty_Mongo::addMaster($connection); 21 | 22 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, My_ShantyMongo_User::getDbName()); 23 | $this->assertEquals('shanty-mongo', My_ShantyMongo_Name::getDbName()); 24 | } 25 | 26 | public function testGetCollectionName() 27 | { 28 | $this->assertEquals('user', My_ShantyMongo_User::getCollectionName()); 29 | } 30 | 31 | public function testGetConnectionGroupName() 32 | { 33 | $this->assertEquals('default', My_ShantyMongo_User::getConnectionGroupName()); 34 | } 35 | 36 | public function testHasDbName() 37 | { 38 | $this->assertTrue(My_ShantyMongo_User::hasDbName()); 39 | $this->assertFalse(My_ShantyMongo_Name::hasDbName()); 40 | } 41 | 42 | public function testHasCollectionName() 43 | { 44 | $this->assertTrue(My_ShantyMongo_User::hasCollectionName()); 45 | $this->assertFalse(My_ShantyMongo_Name::hasCollectionName()); 46 | } 47 | 48 | public function testIsDocumentClass() 49 | { 50 | $this->assertFalse(Shanty_Mongo_Collection::isDocumentClass()); 51 | $this->assertTrue(My_ShantyMongo_User::isDocumentClass()); 52 | } 53 | 54 | public function testGetDocumentClass() 55 | { 56 | $this->assertEquals('My_ShantyMongo_User', My_ShantyMongo_User::getDocumentClass()); 57 | $this->assertEquals('My_ShantyMongo_Name', My_ShantyMongo_Name::getDocumentClass()); 58 | } 59 | 60 | /** 61 | * @expectedException Shanty_Mongo_Exception 62 | */ 63 | public function testGetDocumentClassException() 64 | { 65 | Shanty_Mongo_Collection::getDocumentClass(); 66 | } 67 | 68 | public function testGetDocumentSetClass() 69 | { 70 | $this->assertEquals('Shanty_Mongo_DocumentSet', My_ShantyMongo_Name::getDocumentSetClass()); 71 | } 72 | 73 | public function testMakeRequirementsTidy() 74 | { 75 | $dirty = array( 76 | 'name' => array('Document:My_ShantyMongo_Name', 'Required'), 77 | 'friends' => 'DocumentSet', 78 | 'friends.$' => array('Document:My_ShantyMongo_User', 'AsReference'), 79 | 'sex' => array('Required', 'Validator:InArray' => array('F', 'M')), 80 | ); 81 | 82 | $clean = array( 83 | 'name' => array('Document:My_ShantyMongo_Name' => null, 'Required' => null), 84 | 'friends' => array('DocumentSet' => null), 85 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 86 | 'sex' => array('Required' => null, 'Validator:InArray' => array('F', 'M')), 87 | ); 88 | 89 | $this->assertEquals($clean, Shanty_Mongo_Collection::makeRequirementsTidy($dirty)); 90 | } 91 | 92 | public function testMergeRequirements() 93 | { 94 | $requirements1 = array( 95 | 'name' => array('Validator:Magic' => null, 'Document' => null, 'Required' => null), 96 | 'email' => array('Validator:EmailAddress' => null), 97 | 'friends' => array('DocumentSet:My_ShantyMongo_Users' => null), 98 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 99 | ); 100 | 101 | $requirements2 = array( 102 | 'name' => array('Document:My_ShantyMongo_Name' => null), 103 | 'email' => array('Required' => null), 104 | 'concession' => array('Required' => null), 105 | 'friends' => array('DocumentSet' => null) 106 | ); 107 | 108 | $result = array( 109 | 'name' => array('Validator:Magic' => null, 'Document:My_ShantyMongo_Name' => null, 'Required' => null), 110 | 'email' => array('Validator:EmailAddress' => null, 'Required' => null), 111 | 'friends' => array('DocumentSet' => null), 112 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 113 | 'concession' => array('Required' => null), 114 | ); 115 | 116 | $this->assertEquals($result, Shanty_Mongo_Collection::mergeRequirements($requirements1, $requirements2)); 117 | } 118 | 119 | public function testGetCollectionInheritance() 120 | { 121 | $this->assertEquals(array('My_ShantyMongo_User'), My_ShantyMongo_User::getCollectionInheritance()); 122 | 123 | $inheritance = My_ShantyMongo_Student::getCollectionInheritance(); 124 | $this->assertEquals('My_ShantyMongo_Student', $inheritance[0]); 125 | $this->assertEquals('My_ShantyMongo_User', $inheritance[1]); 126 | 127 | $inheritance = My_ShantyMongo_ArtStudent::getCollectionInheritance(); 128 | $this->assertEquals('My_ShantyMongo_ArtStudent', $inheritance[0]); 129 | $this->assertEquals('My_ShantyMongo_Student', $inheritance[1]); 130 | $this->assertEquals('My_ShantyMongo_User', $inheritance[2]); 131 | } 132 | 133 | /** 134 | * @depends testMakeRequirementsTidy 135 | */ 136 | public function testGetCollectionRequirements() 137 | { 138 | $requirements = array( 139 | 'concession' => array('Required' => null) 140 | ); 141 | 142 | $this->assertEquals($requirements, My_ShantyMongo_Student::getCollectionRequirements(false)); 143 | 144 | $requirements = array( 145 | '_id' => array('Validator:MongoId' => null), 146 | '_type' => array('Array' => null), 147 | 'name' => array('Document:My_ShantyMongo_Name' => null, 'Required' => null), 148 | 'email' => array('Required' => null, 'Validator:EmailAddress' => null), 149 | 'addresses' => array('DocumentSet' => null), 150 | 'addresses.$.street' => array('Required' => null), 151 | 'addresses.$.state' => array('Required' => null), 152 | 'addresses.$.suburb' => array('Required' => null), 153 | 'addresses.$.postcode' => array('Required' => null), 154 | 'friends' => array('DocumentSet:My_ShantyMongo_Users' => null), 155 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 156 | 'sex' => array('Required' => null, 'Validator:InArray' => array('F', 'M')), 157 | 'partner' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 158 | 'concession' => array('Required' => null) 159 | ); 160 | 161 | $this->assertEquals($requirements, My_ShantyMongo_Student::getCollectionRequirements()); 162 | 163 | $requirements = array( 164 | '_id' => array('Validator:MongoId' => null), 165 | '_type' => array('Array' => null), 166 | 'name' => array('Document:My_ShantyMongo_Name' => null, 'Required' => null), 167 | 'email' => array('Required' => null, 'Validator:EmailAddress' => null), 168 | 'addresses' => array('DocumentSet' => null), 169 | 'addresses.$.street' => array('Required' => null), 170 | 'addresses.$.state' => array('Required' => null), 171 | 'addresses.$.suburb' => array('Required' => null), 172 | 'addresses.$.postcode' => array('Required' => null), 173 | 'friends' => array('DocumentSet:My_ShantyMongo_Users' => null), 174 | 'friends.$' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null), 175 | 'sex' => array('Required' => null, 'Validator:InArray' => array('F', 'M')), 176 | 'partner' => array('Document:My_ShantyMongo_User' => null, 'AsReference' => null) 177 | ); 178 | 179 | // This assertion is needed to ensure parent requirements have not been contaminated by child requirements 180 | $this->assertEquals($requirements, My_ShantyMongo_User::getCollectionRequirements()); 181 | } 182 | 183 | public function testGetConnection() 184 | { 185 | $connection = new Shanty_Mongo_Connection('localhost'); 186 | Shanty_Mongo::addSlave($connection); 187 | 188 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, My_ShantyMongo_User::getConnection()); 189 | $this->assertEquals(TESTS_SHANTY_MONGO_CONNECTIONSTRING, My_ShantyMongo_User::getConnection()->getActualConnectionString()); 190 | 191 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, My_ShantyMongo_User::getConnection(false)); 192 | $this->assertEquals('localhost', My_ShantyMongo_User::getConnection(false)->getActualConnectionString()); 193 | } 194 | 195 | public function testGetMongoDb() 196 | { 197 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, My_ShantyMongo_User::getMongoDb()); 198 | $this->assertEquals(TESTS_SHANTY_MONGO_DB, My_ShantyMongo_User::getMongoDb()->__toString()); 199 | } 200 | 201 | /** 202 | * @expectedException Shanty_Mongo_Exception 203 | */ 204 | public function testGetMongoDbException() 205 | { 206 | My_ShantyMongo_Name::getMongoDb(); 207 | } 208 | 209 | public function testGetMongoCollection() 210 | { 211 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, My_ShantyMongo_User::getMongoCollection()); 212 | $this->assertEquals(TESTS_SHANTY_MONGO_DB.'.user', My_ShantyMongo_User::getMongoCollection()->__toString()); 213 | } 214 | 215 | /** 216 | * @expectedException Shanty_Mongo_Exception 217 | */ 218 | public function testGetMongoCollectionException() 219 | { 220 | My_ShantyMongo_Name::getMongoCollection(); 221 | } 222 | 223 | public function testCreate() 224 | { 225 | $document = My_ShantyMongo_User::create(); 226 | 227 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $document); 228 | $this->assertEquals('My_ShantyMongo_User', get_class($document)); 229 | $this->assertEquals('user', $document->getConfigAttribute('collection')); 230 | $this->assertTrue($document->isNewDocument()); 231 | 232 | $document = My_ShantyMongo_User::create(array('email' => 'address@domain.com'), false); 233 | $this->assertEquals('address@domain.com', $document->email); 234 | $this->assertFalse($document->isNewDocument()); 235 | } 236 | 237 | public function testFind() 238 | { 239 | $cherry = My_ShantyMongo_User::find('4c04516f1f5f5e21361e3ab1'); 240 | 241 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $cherry); 242 | $this->assertEquals('My_ShantyMongo_Student', get_class($cherry)); 243 | $this->assertEquals('Cherry', $cherry->name->first); 244 | $this->assertEquals($this->_users['cherry'], $cherry->export()); 245 | $this->assertFalse($cherry->isNewDocument()); 246 | $this->assertEquals('user', $cherry->getConfigAttribute('collection')); 247 | 248 | $cherry = My_ShantyMongo_User::find(new MongoId('4c04516f1f5f5e21361e3ab1')); 249 | 250 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $cherry); 251 | $this->assertEquals('My_ShantyMongo_Student', get_class($cherry)); 252 | $this->assertEquals('Cherry', $cherry->name->first); 253 | $this->assertEquals($this->_users['cherry'], $cherry->export()); 254 | } 255 | 256 | public function testOne() 257 | { 258 | $roger = My_ShantyMongo_User::one(array('name.first' => 'Roger')); 259 | 260 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $roger); 261 | $this->assertEquals('My_ShantyMongo_ArtStudent', get_class($roger)); 262 | $this->assertEquals('Roger', $roger->name->first); 263 | $this->assertEquals($this->_users['roger'], $roger->export()); 264 | 265 | // Find only rodger's name and email 266 | $roger = My_ShantyMongo_User::one(array('name.first' => 'Roger'), array('name' => 1, 'email' => 1)); 267 | $this->assertEquals('My_ShantyMongo_ArtStudent', get_class($roger)); 268 | $this->assertEquals(4, count($roger)); 269 | $this->assertEquals(array('_id', '_type', 'name', 'email'), $roger->getPropertyKeys()); 270 | $this->assertEquals('Roger', $roger->name->first); 271 | $this->assertNull($roger->sex); 272 | 273 | // No teacher by the name of roger exists 274 | $roger = My_ShantyMongo_Teacher::fetchOne(array('name.first' => 'Roger')); 275 | $this->assertNull($roger); 276 | } 277 | 278 | public function testFetchOne() 279 | { 280 | $roger = My_ShantyMongo_User::fetchOne(array('name.first' => 'Roger'), array('name' => 1, 'email' => 1)); 281 | 282 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $roger); 283 | $this->assertEquals('My_ShantyMongo_ArtStudent', get_class($roger)); 284 | $this->assertEquals(array('_id', '_type', 'name', 'email'), $roger->getPropertyKeys()); 285 | $this->assertEquals('Roger', $roger->name->first); 286 | $this->assertNull($roger->sex); 287 | } 288 | 289 | public function testAll() 290 | { 291 | $users = My_ShantyMongo_User::all(); 292 | 293 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $users); 294 | $this->assertEquals('Shanty_Mongo_Iterator_Cursor', get_class($users)); 295 | $this->assertEquals(3, $users->count()); 296 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $users->getNext()->getId()->__toString()); 297 | $this->assertEquals('4c04516f1f5f5e21361e3ab1', $users->getNext()->getId()->__toString()); 298 | $this->assertEquals('4c0451791f5f5e21361e3ab2', $users->getNext()->getId()->__toString()); 299 | 300 | $males = My_ShantyMongo_User::all(array('sex' => 'M')); 301 | 302 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $males); 303 | $this->assertEquals('Shanty_Mongo_Iterator_Cursor', get_class($males)); 304 | $this->assertEquals(2, $males->count()); 305 | $this->assertEquals('4c04516a1f5f5e21361e3ab0', $males->getNext()->getId()->__toString()); 306 | $this->assertEquals('4c0451791f5f5e21361e3ab2', $males->getNext()->getId()->__toString()); 307 | 308 | // Test inheritance 309 | $students = My_ShantyMongo_Student::all(); 310 | $this->assertEquals(2, $students->count()); 311 | $this->assertEquals('4c04516f1f5f5e21361e3ab1', $students->getNext()->getId()->__toString()); 312 | $this->assertEquals('4c0451791f5f5e21361e3ab2', $students->getNext()->getId()->__toString()); 313 | 314 | $artstudents = My_ShantyMongo_ArtStudent::all(); 315 | $this->assertEquals(1, $artstudents->count()); 316 | $this->assertEquals('4c0451791f5f5e21361e3ab2', $artstudents->getNext()->getId()->__toString()); 317 | 318 | // Test loading of partial documents 319 | $users = My_ShantyMongo_User::all(array(), array('name' => 1, 'email' => 1)); 320 | $firstUser = $users->getNext(); 321 | $this->assertEquals(array('_id', '_type', 'name', 'email'), $firstUser->getPropertyKeys()); 322 | $this->assertNull($firstUser->sex); 323 | } 324 | 325 | public function testFetchAll() 326 | { 327 | $users = My_ShantyMongo_User::fetchAll(); 328 | 329 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $users); 330 | $this->assertEquals('Shanty_Mongo_Iterator_Cursor', get_class($users)); 331 | $this->assertEquals(3, $users->count()); 332 | 333 | $males = My_ShantyMongo_User::fetchAll(array('sex' => 'M'), array('name' => 1, 'email' => 1)); 334 | 335 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $males); 336 | $this->assertEquals('Shanty_Mongo_Iterator_Cursor', get_class($males)); 337 | $this->assertEquals(2, $males->count()); 338 | $firstUser = $males->getNext(); 339 | $this->assertEquals(array('_id', '_type', 'name', 'email'), $firstUser->getPropertyKeys()); 340 | $this->assertNull($firstUser->sex); 341 | } 342 | 343 | public function testDistinct() 344 | { 345 | $distinctSexes = My_ShantyMongo_User::distinct('sex'); 346 | $this->assertEquals(array('M', 'F'), $distinctSexes); 347 | } 348 | 349 | public function testDistinctWithQuery() 350 | { 351 | $distinctSexes = My_ShantyMongo_User::distinct('sex', array('name.first' => array('$ne' => 'Cherry'))); 352 | $this->assertEquals(array('M'), $distinctSexes); 353 | } 354 | 355 | public function testInsert() 356 | { 357 | $sarah = array( 358 | '_id' => new MongoId('4c04d5101f5f5e21361e3ab5'), 359 | 'name' => array( 360 | 'first' => 'Sarah', 361 | 'last' => 'Thomas', 362 | ), 363 | 'email' => 'bob.jones@domain.com', 364 | 'sex' => 'M' 365 | ); 366 | 367 | My_ShantyMongo_User::insert($sarah, array('safe' => true)); 368 | 369 | $user = My_ShantyMongo_User::find('4c04d5101f5f5e21361e3ab5'); 370 | 371 | $this->assertInternalType(PHPUnit_Framework_Constraint_IsType::TYPE_OBJECT, $user); 372 | $this->assertEquals('My_ShantyMongo_User', get_class($user)); 373 | $this->assertEquals('Sarah', $user->name->first); 374 | 375 | 376 | $users = My_ShantyMongo_User::all(); 377 | $this->assertEquals(4, $users->count()); 378 | } 379 | 380 | public function testInsertBatch() 381 | { 382 | $data = array( 383 | array( 384 | '_id' => new MongoId('4c04d5101f5f5e21361e3ab6'), 385 | 'name' => 'green', 386 | 'hex' => '006600' 387 | ), 388 | array( 389 | '_id' => new MongoId('4c04d5101f5f5e21361e3ab7'), 390 | 'name' => 'blue', 391 | 'hex' => '0000CC' 392 | ), 393 | array( 394 | '_id' => new MongoId('4c04d5101f5f5e21361e3ab8'), 395 | 'name' => 'red', 396 | 'hex' => 'FF0000' 397 | ), 398 | ); 399 | 400 | My_ShantyMongo_Simple::insertBatch($data, array('safe' => true)); 401 | $colours = My_ShantyMongo_Simple::all(); 402 | 403 | $colour = $colours->getNext(); 404 | $this->assertEquals('green', $colour->name); 405 | $this->assertEquals(3, $colours->count()); 406 | } 407 | 408 | /** 409 | * @depends testFind 410 | */ 411 | public function testUpdate() 412 | { 413 | My_ShantyMongo_User::update(array('_id' => new MongoId('4c04516f1f5f5e21361e3ab1')), array('$set' => array('name.first' => 'Lauren')), array('safe' => true)); 414 | 415 | $lauren = My_ShantyMongo_User::find('4c04516f1f5f5e21361e3ab1'); 416 | $this->assertEquals('Lauren', $lauren->name->first); 417 | } 418 | 419 | /** 420 | * @depends testFind 421 | */ 422 | public function testRemove() 423 | { 424 | My_ShantyMongo_User::remove(array('name.first' => 'Bob'), array('safe' => true)); 425 | 426 | $bob = My_ShantyMongo_User::find('4c04516a1f5f5e21361e3ab0'); 427 | 428 | $this->assertNull($bob); 429 | 430 | $users = My_ShantyMongo_User::all(); 431 | $this->assertEquals(2, $users->count()); 432 | } 433 | 434 | public function testDrop() 435 | { 436 | My_ShantyMongo_User::drop(); 437 | 438 | $users = My_ShantyMongo_User::all(); 439 | 440 | $this->assertEquals(0, $users->count()); 441 | } 442 | 443 | public function testGetIndexInfo() 444 | { 445 | $indexInfo = array( 446 | array( 447 | 'name' => '_id_', 448 | 'ns' => 'shanty-mongo-testing.user', 449 | 'key' => array( 450 | '_id' => 1 451 | ), 452 | 'v' => 1 453 | ) 454 | ); 455 | 456 | $this->assertEquals($indexInfo, My_ShantyMongo_User::getIndexInfo()); 457 | } 458 | 459 | public function testEnsureIndex() 460 | { 461 | My_ShantyMongo_User::ensureIndex(array('name.first' => 1), array('safe' => true)); 462 | 463 | $indexInfo = array( 464 | array( 465 | 'name' => '_id_', 466 | 'ns' => 'shanty-mongo-testing.user', 467 | 'key' => array( 468 | '_id' => 1 469 | ), 470 | 'v' => 1 471 | ), 472 | array( 473 | '_id' => new MongoId(), 474 | 'ns' => 'shanty-mongo-testing.user', 475 | 'key' => array( 476 | 'name.first' => 1 477 | ), 478 | 'name' => 'name_first_1', 479 | 'v' => 1 480 | ) 481 | ); 482 | 483 | $retrievedIndexInfo = My_ShantyMongo_User::getIndexInfo(); 484 | 485 | $this->assertEquals($indexInfo[0], $retrievedIndexInfo[0]); 486 | $this->assertEquals($indexInfo[1]['ns'], $retrievedIndexInfo[1]['ns']); 487 | $this->assertEquals($indexInfo[1]['key'], $retrievedIndexInfo[1]['key']); 488 | $this->assertEquals($indexInfo[1]['name'], $retrievedIndexInfo[1]['name']); 489 | } 490 | 491 | /** 492 | * @depends testEnsureIndex 493 | */ 494 | public function testDeleteIndex() 495 | { 496 | My_ShantyMongo_User::ensureIndex(array('name.first' => 1), array('safe' => true)); 497 | My_ShantyMongo_User::deleteIndex('name.first'); 498 | 499 | $indexInfo = array( 500 | array( 501 | 'name' => '_id_', 502 | 'ns' => 'shanty-mongo-testing.user', 503 | 'key' => array( 504 | '_id' => 1 505 | ), 506 | 'v' => 1 507 | ) 508 | ); 509 | 510 | $this->assertEquals($indexInfo, My_ShantyMongo_User::getIndexInfo()); 511 | } 512 | 513 | /** 514 | * @depends testEnsureIndex 515 | */ 516 | public function testDeleteIndexes() 517 | { 518 | My_ShantyMongo_User::ensureIndex(array('name.first' => 1), array('safe' => true)); 519 | My_ShantyMongo_User::deleteIndexes(); 520 | 521 | $indexInfo = array( 522 | array( 523 | 'name' => '_id_', 524 | 'ns' => 'shanty-mongo-testing.user', 525 | 'key' => array( 526 | '_id' => 1 527 | ), 528 | 'v' => 1 529 | ) 530 | ); 531 | 532 | $this->assertEquals($indexInfo, My_ShantyMongo_User::getIndexInfo()); 533 | } 534 | } -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | RETIRED 2 | ======= 3 | 4 | This Library has be retired until further notice. There are a number of critical bugs that this library contains. They can be fixed but I don't have the time at the moment and I feel it is irresponsible to let people continue to use this library knowing the issues they may experience. I welcome pull requests! With tests of course! 5 | 6 | Shanty Mongo 7 | ============ 8 | 9 | [![Build Status](https://secure.travis-ci.org/coen-hyde/Shanty-Mongo.png?branch=master)](http://travis-ci.org/coen-hyde/Shanty-Mongo) 10 | 11 | Summary 12 | ------- 13 | 14 | Shanty Mongo is MongoDB library for the Zend Framework. It's intention is to make working with MongoDB documents as natural and as simple as possible. In particular allowing embedded documents to also have custom document classes. 15 | 16 | Requirements 17 | ------------ 18 | 19 | - PHP 5.3 or greater 20 | - Zend Framework 1.10.0 or greater 21 | - Mongodb 1.3 or greater 22 | - Mongo extension from pecl 23 | 24 | Features 25 | -------- 26 | 27 | - ORM 28 | - Simple and flexible 29 | - Partial updates. Only changed data is sent back to the server. Also you can save or delete embeded documents individually. 30 | - Support for references (lazy loaded) 31 | - Support for inheritance 32 | - Optional schema enforcement: Validation and Filtering on properties 33 | - Embeded documents/documentsets can have custom document classes like the root document 34 | 35 | How to Use 36 | ---------- 37 | 38 | ### Initialisation 39 | 40 | Use Zend's autoloader and add the library folder to your include path 41 | 42 | ### Connections 43 | 44 | If you are connecting to localhost without any authentication then no need to worry about connections any further. Shanty Mongo will connect automatically on the first request if no connections have previously been added. 45 | 46 | #### Advanced connections 47 | 48 | For information on how to configure master/slave setups, weighted connections and multiple connection goups see the [wiki](http://wiki.github.com/coen-hyde/Shanty-Mongo/connections) 49 | 50 | ### Define a document/collection 51 | 52 | To define a document and the collection that the document will be saved to, extend Shanty_Mongo_Document and set the static properties $\_db and $\_collection. 53 | 54 | class User extends Shanty_Mongo_Document 55 | { 56 | protected static $_db = 'forum'; 57 | protected static $_collection = 'user'; 58 | } 59 | 60 | ### Create a new document 61 | 62 | $user = new User(); 63 | $user->name = 'Bob'; 64 | $user->save(); 65 | 66 | // Or you can pass an array of data to the constructor as so. 67 | // Please be aware passing data into the constructor by passes filtering and validation 68 | // It assumes you are passing in a raw 'json' document from mongo 69 | $data = array( 70 | 'name' => 'Bob' 71 | ); 72 | 73 | $user = new User($data); 74 | $user->save(); 75 | 76 | ### Find a document 77 | 78 | $user = User::find($id); 79 | 80 | $id can either be a string representation of the document id or an instance of MongoId. 81 | 82 | ### Adding requirements 83 | 84 | There are 3 types of requirements. Validators, filters and special. 85 | 86 | #### Validators 87 | 88 | To use a validator add a requirement with the prefix 'Validator:' followed by the name of the validator. Please see the Zend reference guide for the list of [Zend validators](http://framework.zend.com/manual/en/zend.validate.set.html). I addition to the validators supported by Zend, Shanty Mongo supports the following validators: 89 | 90 | - Validator:Array 91 | 92 | - Validator:MongoId 93 | 94 | #### Filters 95 | 96 | To use a filter add a requirement with the prefix 'Filter:' followed by the name of the filter. Please see the Zend reference guide for the list of [Zend filters](http://framework.zend.com/manual/en/zend.filter.set.html). 97 | 98 | #### Requirements with special meaning or behaviour 99 | 100 | - Document:{ClassName} 101 | Validates a property is of type ClassName and lazy loads an instance of the document on first access. If no ClassName is provided then Shanty_Mongo_Document is assumed. eg 'Document:User' or without a class name 'Document'. 102 | 103 | - DocumentSet:{ClassName} 104 | Validates a property is of type ClassName and lazy loads an instance of the documentset on first access. If no ClassName is provided then Shanty_Mongo_DocumentSet is assumed. eg 'DocumentSet:Posts' or without a class name 'DocumentSet'. 105 | 106 | - Required 107 | Ensures that a property exists. Unlike most validators that run when a property is set, Required is run when a document is saved. 108 | 109 | - Ignore 110 | Will prevent a property/field being saved to Mongo. Allows overriding export() function and adding own computed data without persisting back to db. 111 | 112 | - AsReference 113 | Will save a document as a reference 114 | 115 | #### Lets put some of them to use 116 | 117 | class User extends Shanty_Mongo_Document 118 | { 119 | protected static $_db = 'forum'; 120 | protected static $_collection = 'user'; 121 | 122 | protected static $_requirements = array( 123 | 'name' => 'Required', 124 | 'email' => array('Required', 'Validator:EmailAddress'), 125 | 'friends' => 'DocumentSet', 126 | 'friends.$' => array('Document:User', 'AsReference') 127 | ); 128 | } 129 | 130 | There is a lot going on here so i don't expect you to understand what is happening just yet. 131 | 132 | Even though there are 4 keys in the requirement list we are actually only specifying requirements for 3 properties. The last two 'friends' and 'friends.$' both refer to the 'friends' property. 133 | 134 | We have enforced that both the properties 'name' and 'email' are required while 'friends' is optional. We have also stated that the 'email' property must be an email address. When an attempt to set the 'email' property is made, the value will be run through the validator Zend_Validate_EmailAddress. If it fails validation an exception will be thrown. If you wanted to determine if an email address is valid without throwing an exception call $user->isValid('email', 'invalid@email#address.com'); 135 | 136 | The property 'friends' is a document set and all it's elements are documents of type 'User'. When this document set is saved all the 'User' documents will be saved as references. More on document sets later. 137 | 138 | #### Validators and Filters with options 139 | 140 | Some validators and filters have additional options that need to be passed to it's constructor. This can be achieve by setting the requirement as the key and the options as the value. As a demonstration we'll add a sex property on the user object and use the InArray validator. 141 | 142 | class User extends Shanty_Mongo_Document 143 | { 144 | protected static $_db = 'forum'; 145 | protected static $_collection = 'user'; 146 | 147 | protected static $_requirements = array( 148 | 'name' => 'Required', 149 | 'email' => array('Required', 'Validator:EmailAddress'), 150 | 'friends' => 'DocumentSet', 151 | 'friends.$' => array('Document:User', 'AsReference'), 152 | 'sex' => array('Validator:InArray' => array('female', 'male'); 153 | ); 154 | } 155 | 156 | ### Creating embedded documents 157 | 158 | Say we wanted to also store the users last name. We could have nameFirst and nameLast properties on the document but in the spirit of document databases we'll make the property 'name' an embedded document with the properties first and last. 159 | 160 | $user = new User(); 161 | $user->name = new Shanty_Mongo_Document(); 162 | $user->name->first = 'Bob'; 163 | $user->name->last = 'Jane'; 164 | $user->save(); 165 | 166 | Since we know all users must have a first and last name lets enforce it 167 | 168 | class User extends Shanty_Mongo_Document 169 | { 170 | protected static $_db = 'forum'; 171 | protected static $_collection = 'user'; 172 | 173 | protected static $_requirements = array( 174 | 'name' => array('Document', 'Required'), 175 | 'name.first' => 'Required', 176 | 'name.last' => 'Required', 177 | 'email' => array('Required', 'Validator:EmailAddress'), 178 | ); 179 | } 180 | 181 | Notice how i've given the property 'name' the requirement of 'Document'? Now we do not have to initialise a new document when we set a users name. The name document is lazy loaded the first time we try to access it. 182 | 183 | $user = new User(); 184 | $user->name->first = 'Bob'; 185 | $user->name->last = 'Jane'; 186 | $user->save(); 187 | 188 | ### Saving embedded documents 189 | 190 | A nice feature is the ability to save embedded documents independently. eg. 191 | 192 | $user = User::find($id); 193 | $user->name->last = 'Tmart'; 194 | $user->name->save(); 195 | 196 | The above example may be a bit pointless but as your documents grow it will feel 'right' to call save on the document you are changing. It's also handy for when you want to pass embedded document around your application without having to remember where they came from. 197 | 198 | No matter where save is called only the changes for that document and all it's children are sent to the database. 199 | 200 | ### Custom embedded document classes. 201 | 202 | Now that we have stored the users first and last names, more than likely will will want to display the users full name. Instead of concatenating the users first and last name every time, we can make 'name' a custom document with a full() method. 203 | 204 | First we'll define the name document 205 | 206 | class Name extends Shanty_Mongo_Document 207 | { 208 | protected static $_requirements = array( 209 | 'first' => 'Required', 210 | 'last' => 'Required', 211 | ); 212 | 213 | public function full() 214 | { 215 | return $this->first.' '.$this->last; 216 | } 217 | } 218 | 219 | Next we'll tell the user document to use our new document 220 | 221 | class User extends Shanty_Mongo_Document 222 | { 223 | protected static $_db = 'forum'; 224 | protected static $_collection = 'user'; 225 | 226 | protected static $_requirements = array( 227 | 'name' => array('Document:Name', 'Required'), 228 | 'email' => array('Required', 'Validator:EmailAddress'), 229 | ); 230 | } 231 | 232 | Now lets use our new document 233 | 234 | $user = User::find($id); 235 | 236 | // prints 'Bob Jane' 237 | print($user->name->full()); 238 | 239 | // You could also add a __toString() method and do something like this 240 | print($user->name); 241 | 242 | ### DocumentSets 243 | 244 | Document sets are actually documents themselves but designed to handle a set of other documents. Think of DocumentSets as an array with extras. You may want to use a DocumentSet to store a list of friends or addresses. 245 | 246 | Lets store a list of addresses against a user. First we must inform the User document of our new requirements 247 | 248 | class User extends Shanty_Mongo_Document 249 | { 250 | protected static $_db = 'forum'; 251 | protected static $_collection = 'user'; 252 | 253 | protected static $_requirements = array( 254 | 'name' => array('Document:Name', 'Required'), 255 | 'email' => array('Required', 'Validator:EmailAddress'), 256 | 'addresses' => 'DocumentSet', 257 | 'addresses.$.street' => 'Required', 258 | 'addresses.$.suburb' => 'Required', 259 | 'addresses.$.state' => 'Required', 260 | 'addresses.$.postCode' => 'Required' 261 | ); 262 | } 263 | 264 | First thing you are probably noticing is the $. The $ is a mask for the array position of any document in the set. Requirements specified against the $ will be applied to all elements. In the above example we are enforcing that all document added to the 'addresses' document set have a bunch of properties. 265 | 266 | There are few different ways you can use DocumentSets. I'll start with the most common usage. 267 | 268 | $user = User::find($id); 269 | 270 | $address = $user->addresses->new(); 271 | $address->street = '88 Hill St'; 272 | $address->suburb = 'Brisbane'; 273 | $address->state = 'QLD'; 274 | $address->postCode = '4000'; 275 | $address->save(); 276 | 277 | There is a bit of magic going on here. First we create a new address. The new method on a DocumentSet returns a new document, by default it will be a Shanty_Mongo_Document. We do our business then save. Save will do a $push operation on $user->addresses with our new document. This is in my opinion the ideal way to add new elements to a document set. Because we a doing a $push operation we do not run the risk of a confict on indexes 278 | 279 | We could have also added the new document to the document set like this 280 | 281 | $user = User::find($id); 282 | 283 | $address = $user->addresses->new(); 284 | $address->street = '88 Hill St'; 285 | $address->suburb = 'Brisbane'; 286 | $address->state = 'QLD'; 287 | $address->postCode = '4000'; 288 | 289 | // add address to addresses 290 | $user->addresses->addDocument(address); 291 | 292 | // Is the same as 293 | //$user->addresses[] = address; 294 | 295 | // Or we could have specified the index directly if we really knew what we were doing 296 | // $user->addresses[4] = address; 297 | 298 | $user->addresses->save(); 299 | 300 | This method may be preferred in certain circumstances 301 | 302 | ### Fetching multiple documents 303 | 304 | We can fetch multiple documents by calling all. All will return a Shanty_Mongo_Iterator_Cursor that has all the functionality of MongoCursor 305 | 306 | Find all users and print their names 307 | 308 | $users = User::all(); 309 | 310 | foreach ($users as $user) { 311 | print($user->name->full()."
\n"); 312 | } 313 | 314 | All also accepts queries. 315 | 316 | Find all users with the first name Bob 317 | 318 | $users = User::all(array('name.first' => 'Bob')); 319 | 320 | Just as with finding a single document you can limit the fields that Shanty Mongo will pull down. 321 | 322 | $users = User::all(array(), array('name' => 1, 'email' => 1); 323 | 324 | This will return only the name and email address for all users. 325 | 326 | ### Using Skip, Limit, Sort etc 327 | 328 | Since the shanty mongo cursor returned by the all method is a subclass of MongoCursor you have all the functionality that is usually available to you as if you were querying mongodb directy. eg 329 | 330 | $users = User::all()->skip(10)->limit(5); 331 | 332 | This will skip the first 10 users and limit the result set to 5 users. Even though it may appear as though we are fetching all the users then skipping and limiting the result set on the php end, this is not the case. The nice thing about the way the Mongo implements cursors is that no results are fetched from the database until the method getNext is called, directly or indirectly. This means that the above skip and limit will only fetch 5 users from the database. 333 | 334 | ### Deleting documents 335 | 336 | To delete a document simply call the method delete(). You can call delete() on root documents or embedded documents. eg 337 | 338 | $user = User::find($id); 339 | 340 | // Delete the name document 341 | $user->name->delete(); 342 | 343 | // Delete the entire document 344 | $user->delete(); 345 | 346 | ### Deleting from a collection 347 | 348 | Maybe you just want to delete all users with the first name John without fetching and initialising all the John documents 349 | 350 | User::remove(array('name.first' => 'John')); 351 | 352 | If you would like that operation to be safe remember to pass the safe flag 353 | 354 | User::remove(array('name.first' => 'John'), array('safe' => true)); 355 | 356 | ### Batch Inserting 357 | 358 | Sometimes you just want to save a whole bunch of stuff to the database without the extra overhead of initialising documents. 359 | 360 | $users = array( 361 | array( 362 | 'name' => array( 363 | 'first' => 'John', 364 | 'last' => 'Mackison' 365 | ), 366 | 'email' => 'john@mackison.com' 367 | ), 368 | array( 369 | 'name' => array( 370 | 'first' => 'Joan', 371 | 'last' => 'Mackison' 372 | ), 373 | 'email' => 'joan@mackison.com' 374 | ) 375 | ); 376 | 377 | User::insertBatch($users); 378 | 379 | This will insert two users into the user collection. A word or warning; batch inserting bypasses all validation and filtering. 380 | 381 | ### Operations 382 | 383 | Operations are queued until a document is saved. 384 | 385 | Lets increment a users post count by one 386 | 387 | $user = User::find($id); 388 | 389 | $user->inc('posts', 1); 390 | $user->save(); 391 | 392 | // Is the same as 393 | $user->addOperation('$inc', 'posts', 1); 394 | $user->save(); 395 | 396 | Operations also work fine on subdocuments 397 | 398 | $user->name->addOperation('$set', 'first', 'Bob); 399 | $user->name->save(); 400 | 401 | // This would also work 402 | $user->save(); 403 | 404 | ### Inheritance 405 | 406 | As of 0.3 Shanty Mongo supports inheritance 407 | 408 | Class User extends Shanty_Mongo_Document 409 | { 410 | protected static $_db = 'lms'; 411 | protected static $_collection = 'user'; 412 | protected static $_requirements = array( 413 | 'name' => array('Document:Name', 'Required'), 414 | 'email' => 'Validator:EmailAddress' 415 | ); 416 | } 417 | 418 | Class Student extends User 419 | { 420 | protected static $_requirements = array( 421 | 'email' => 'Required', 422 | 'classes' => 'DocumentSet' 423 | ); 424 | } 425 | 426 | Class SchoolCaptain extends Student 427 | { 428 | protected static $_requirements = array( 429 | 'obligations' => 'Array' 430 | ); 431 | } 432 | 433 | In the above User, Student and SchoolCaptain will be saved in the user collection. Even though it looks like the requirements in User are being over-ridden by the requirements in Student and SchoolCaptain but they are not. Using some static magic they are actually merged. 434 | 435 | So the effective requirements for SchoolCaptain would be: 436 | 437 | array( 438 | 'name' => array('Document:Name', 'Required'), 439 | 'email' => array('Required', 'Validator:EmailAddress'), 440 | 'classes' => 'DocumentSet', 441 | 'obligations' => 'Array', 442 | ); 443 | 444 | #### Querying for subclasses is easy 445 | 446 | $users = User::all(); // Returns all Users 447 | 448 | foreach ($users as $user) { 449 | print(get_class($user)); // Will print either User, Student or SchoolCaptain 450 | } 451 | 452 | Student::all(array('name.first' => 'Bob')); // Returns only Students with the first name of 'Bob' 453 | SchoolCaptain::all(); // Returns only school captains 454 | 455 | Before you jump in and use inheritance all over the place just be aware that searching subclasses will query the attribute '_type' so be sure to index it for use in production. 456 | 457 | $users = User::all(); // No lookup on '_type' 458 | $students = Student::all(); // A lookup on '_type' is used 459 | $schoolCaptains = SchoolCaptain::all(); // A lookup on '_type' is used 460 | 461 | ### Hooks 462 | 463 | The following hooks are available: 464 | 465 | ##### init() 466 | 467 | Executed after the constructor has finished 468 | 469 | ##### preInsert() 470 | 471 | Executed before saving a new document 472 | 473 | ##### postInsert() 474 | 475 | Executed after saving a new document 476 | 477 | ##### preUpdate() 478 | 479 | Executed before saving an existing document 480 | 481 | ##### postUpdate() 482 | 483 | Executed after saving an existing document 484 | 485 | ##### preSave() 486 | 487 | Executed before saving a document 488 | 489 | ##### postSave() 490 | 491 | Executed after saving a document 492 | 493 | ##### preDelete() 494 | 495 | Executed before deleting a document 496 | 497 | ##### postDelete() 498 | 499 | Executed after deleting a document 500 | 501 | #### Using the Hooks 502 | 503 | To use one of the above hooks simply define a protected method in you document with the name of the hook eg. 504 | 505 | Class User extends Shanty_Mongo_Document 506 | { 507 | protected static $_db = 'forum'; 508 | protected static $_collection = 'user'; 509 | 510 | protected function init() 511 | { 512 | // Do stuff on initialising document 513 | } 514 | } 515 | 516 | Running Tests 517 | ---------- 518 | 519 | Shanty has good test coverage. It's easy to run the tests: 520 | 521 | - Run `composer install --dev` 522 | - Run `./vendor/bin/phpunit` 523 | 524 | If needed, you can change the MongoDB connection string, or the database name by editing the `phpunit.xml.dist` file. 525 | 526 | All tests should pass! 527 | 528 | Community 529 | --------- 530 | 531 | [Shanty Mongo Google Group](https://groups.google.com/forum/?fromgroups#!forum/shanty-mongo) 532 | 533 | Project Organisers 534 | ------------------ 535 | 536 | [tholder](http://github.com/tholder) 537 | 538 | [jwpage](http://github.com/jwpage) 539 | 540 | [settermjd](http://github.com/settermjd) 541 | 542 | [coen-hyde](http://github.com/coen-hyde) 543 | 544 | Special thanks to 545 | ----------------- 546 | 547 | [tonymillion](https://github.com/tonymillion) 548 | 549 | [stunti](http://github.com/stunti) 550 | 551 | [sh](http://github.com/sh) for Shanty_Paginator 552 | 553 | Mongoid for inspiration 554 | --------------------------------------------------------------------------------