├── 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 | [](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 |
--------------------------------------------------------------------------------