├── .gitignore
├── CHANGELOG.md
├── tests
├── Model
│ ├── Note.php
│ ├── Comment.php
│ ├── Review.php
│ ├── Connected.php
│ ├── Author.php
│ ├── Article.php
│ └── Simple.php
├── bootstrap.php
├── Mapper
│ └── AuthorMapper.php
└── Mawelous
│ └── Yamop
│ └── Tests
│ ├── BaseTest.php
│ ├── ConnectionTest.php
│ ├── ModelTest.php
│ └── MapperTest.php
├── src
└── Mawelous
│ └── Yamop
│ ├── Transaction.php
│ ├── Model.php
│ └── Mapper.php
├── composer.json
├── phpunit.xml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.settings
2 | /.buildpath
3 | /.project
4 | /vendor
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.2.1 - 2013-11-27
4 | + multiple connections
5 | + numeric arrays output format
--------------------------------------------------------------------------------
/tests/Model/Note.php:
--------------------------------------------------------------------------------
1 | add('Mawelous\Yamop\Tests', __DIR__ );
5 | $loader->add('Model', __DIR__ );
6 | $loader->add('Mapper', __DIR__ );
--------------------------------------------------------------------------------
/tests/Mapper/AuthorMapper.php:
--------------------------------------------------------------------------------
1 | '\Model\Note'
13 | );
14 |
15 | protected static $_embeddedObjectList = array (
16 | 'comments' => '\Model\Comment',
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Model/Simple.php:
--------------------------------------------------------------------------------
1 | =5.3.0"
17 | },
18 | "autoload": {
19 | "psr-0": { "Mawelous\\Yamop": "src/" }
20 | }
21 | }
--------------------------------------------------------------------------------
/tests/Mawelous/Yamop/Tests/BaseTest.php:
--------------------------------------------------------------------------------
1 | {self::$_database};
15 | \Mawelous\Yamop\Mapper::setDatabase( self::$_dbConnection );
16 | }
17 |
18 | public function tearDown()
19 | {
20 | self::$_dbConnection->drop();
21 | }
22 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | ./tests/
12 |
13 |
14 | ./tests/Mawelous/Yamop/Tests/ModelTest.php
15 |
16 |
17 | ./tests/Mawelous/Yamop/Tests/MapperTest.php
18 |
19 |
20 | ./tests/Mawelous/Yamop/Tests/ConnectionTest.php
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/Mawelous/Yamop/Tests/ConnectionTest.php:
--------------------------------------------------------------------------------
1 | {self::$_database1};
17 | self::$_dbConnection2 = $connection->{self::$_database2};
18 |
19 | \Mawelous\Yamop\Mapper::setDatabase(
20 | array( 'first' => self::$_dbConnection1,
21 | 'second' => self::$_dbConnection2 )
22 | );
23 | }
24 |
25 | public function testSaveWithoutSpecifiedConnection()
26 | {
27 | $simple = new \Model\Simple( $this->_getSimpleData() );
28 | $simple->save();
29 |
30 | $rawObject = self::$_dbConnection1->simple
31 | ->findOne( array( '_id' => $simple->_id ) );
32 |
33 | $this->assertInternalType( 'array', $rawObject );
34 |
35 | return $simple;
36 |
37 | }
38 |
39 | public function testFindWithoutSpecifiedConnection()
40 | {
41 | $this->_saveDataInFirst();
42 | $found = \Model\Simple::findOne( $this->_getSimpleData() );
43 | $this->assertInstanceOf( '\Model\Simple', $found );
44 | }
45 |
46 | public function testSaveWithSpecifiedConnection()
47 | {
48 | $connected = new \Model\Connected( $this->_getSimpleData() );
49 | $connected->save();
50 |
51 | $rawObject = self::$_dbConnection2->connected
52 | ->findOne( array( '_id' => $connected->_id ) );
53 |
54 | $this->assertInternalType( 'array', $rawObject );
55 |
56 | return $connected;
57 |
58 | }
59 |
60 | public function testFindWithSpecifiedConnection()
61 | {
62 | $this->_saveDataInSecond();
63 | $connected = \Model\Connected::findOne( $this->_getSimpleData() );
64 | $this->assertInstanceOf( '\Model\Connected', $connected );
65 | }
66 |
67 | public function tearDown()
68 | {
69 | self::$_dbConnection1->drop();
70 | self::$_dbConnection2->drop();
71 | }
72 |
73 | protected function _getSimpleData()
74 | {
75 | return array( 'test' => 'test' );
76 | }
77 |
78 | protected function _saveDataInFirst()
79 | {
80 | self::$_dbConnection1->simple->insert( $this->_getSimpleData() );
81 | return $this->_getSimpleData();
82 | }
83 |
84 | protected function _saveDataInSecond()
85 | {
86 | self::$_dbConnection2->connected->insert( $this->_getSimpleData() );
87 | return $this->_getSimpleData();
88 | }
89 | }
--------------------------------------------------------------------------------
/tests/Mawelous/Yamop/Tests/ModelTest.php:
--------------------------------------------------------------------------------
1 | _articleId = new \MongoId();
17 | $this->_authorId = new \MongoId();
18 | $this->_reviewIds = array( new \MongoId(), new \MongoId() );
19 | $this->_timestamp = strtotime( '13-12-2012' );
20 | }
21 |
22 | public function testFill()
23 | {
24 | $article = $this->_getArticle();
25 | $articleData = $this->_getArticleData();
26 | $commentData = $this->_getCommentData();
27 |
28 | $this->assertSame( $articleData[ 'title' ], $article->title );
29 | $this->assertSame( $articleData[ 'text' ], $article->text);
30 | $this->assertInstanceOf( '\Model\Note', $article->note);
31 |
32 | $comment = current( $article->comments);
33 | $this->assertInstanceOf( '\Model\Comment', $comment );
34 | $this->assertSame( $commentData[ 'text' ], $comment->text );
35 | $this->assertSame( $commentData[ 'date' ]->sec, $comment->date->sec );
36 |
37 | return $article;
38 | }
39 |
40 | public function testGetMapper()
41 | {
42 | $this->assertInstanceOf( 'Mawelous\Yamop\Mapper', \Model\Article::getMapper() );
43 | $this->assertInstanceOf( '\Mapper\AuthorMapper', \Model\Author::getMapper() );
44 | }
45 |
46 | public function testCollectionName()
47 | {
48 | $this->assertSame( 'authors', \Model\Author::getCollectionName() );
49 | }
50 |
51 | /**
52 | * @expectedException Exception
53 | */
54 | public function testNoCollectionName()
55 | {
56 | \Model\Comment::getCollectionName();
57 | }
58 |
59 | /**
60 | * @depends testFill
61 | */
62 | public function testSave( \Model\Article $article )
63 | {
64 | $result = $article->save();
65 |
66 | $this->assertArrayHasKey( 'ok', $result );
67 | $this->assertEquals( 1, $result[ 'ok' ] );
68 | $this->assertObjectHasAttribute( 'id', $article );
69 | $this->assertObjectHasAttribute( '_id', $article );
70 |
71 | $rawArticle = self::$_dbConnection->articles
72 | ->findOne( array( '_id' => $article->_id ) );
73 |
74 | $this->assertInternalType( 'array', $rawArticle );
75 |
76 | return $article;
77 |
78 | }
79 |
80 | /**
81 | * @depends testSave
82 | */
83 | public function testSaveWithoutStringIds( \Model\Article $article )
84 | {
85 |
86 | $rawArticle = self::$_dbConnection->articles
87 | ->findOne( array( '_id' => $article->_id ) );
88 |
89 | $this->assertFalse( isset( $rawArticle['id'] ));
90 | $this->assertFalse( isset( $rawArticle['author']['id'] ));
91 | $this->assertFalse( isset( $rawArticle['comments'][0]['id'] ));
92 |
93 | }
94 |
95 | public function testRemove()
96 | {
97 | $article = $this->_insertArticle();
98 | $article->remove();
99 |
100 | $result = self::$_dbConnection->articles->findOne( array( '_id' => $article->_id ) );
101 |
102 | $this->assertSame( null, $result );
103 | }
104 |
105 | public function testFindById()
106 | {
107 | $article = $this->_insertArticle();
108 |
109 | $dbArticleByString = \Model\Article::findById( $article->id );
110 | $dbArticleByMongoId = \Model\Article::findById( $article->_id );
111 |
112 | $this->assertInstanceOf( '\Model\Article', $dbArticleByString );
113 | $this->assertInstanceOf( '\Model\Article', $dbArticleByMongoId );
114 |
115 | }
116 |
117 | public function testFindOne()
118 | {
119 | $article = $this->_insertArticle();
120 |
121 | $result = \Model\Article::findOne( array ('title' => $article->title ) );
122 |
123 | $this->assertInstanceOf( '\Model\Article', $result);
124 | $this->assertEquals( $article->id, $result->id );
125 | }
126 |
127 | public function testFind()
128 | {
129 | $article = $this->_insertArticle();
130 |
131 | $result = \Model\Article::find( array ('title' => $article->title ) );
132 |
133 | $this->assertInstanceOf( '\Mawelous\Yamop\Mapper', $result);
134 |
135 | $cursor = $result->getCursor();
136 |
137 | $this->assertEquals( 1, count( $cursor ) );
138 |
139 | }
140 |
141 | public function testJoinOneWithFieldName()
142 | {
143 | $article = $this->_getArticle();
144 | $author = $this->_insertAuthor();
145 |
146 | $article->joinOne( 'author_id', '\Model\Author', 'author' );
147 | $this->assertAttributeInstanceOf( '\Model\Author', 'author', $article );
148 |
149 | }
150 |
151 | public function testJoinOneWithoutFieldName()
152 | {
153 | $article = $this->_getArticle();
154 | $author = $this->_insertAuthor();
155 |
156 | $article->joinOne( 'author_id', '\Model\Author' );
157 | $this->assertAttributeInstanceOf( '\Model\Author', 'author_id', $article );
158 |
159 | }
160 |
161 | public function testJoinOneWithLimitedFields()
162 | {
163 | $article = $this->_getArticle();
164 | $author = $this->_insertAuthor();
165 |
166 | $article->joinOne( 'author_id', '\Model\Author', 'author', array( 'name' ) );
167 |
168 | $article->joinOne( 'author_id', '\Model\Author' );
169 | $this->assertAttributeInstanceOf( '\Model\Author', 'author', $article );
170 | $this->assertFalse( isset( $article->author->email ) );
171 |
172 | }
173 |
174 | public function testJoinManyWithFieldName()
175 | {
176 | $article = $this->_getArticle();
177 | $article->review_ids = $this->_reviewIds;
178 | $this->_insertReviews();
179 |
180 | $article->joinMany( 'review_ids', '\Model\Review', 'reviews' );
181 |
182 | $this->assertInternalType( 'array', $article->reviews );
183 |
184 | $review = array_shift( $article->reviews );
185 | $this->assertInstanceOf( '\Model\Review', $review );
186 |
187 | }
188 |
189 | public function testJoinManyWithoutFieldName()
190 | {
191 | $article = $this->_getArticle();
192 | $article->reviews = $this->_reviewIds;
193 | $this->_insertReviews();
194 |
195 | $article->joinMany( 'reviews', '\Model\Review' );
196 |
197 | $this->assertInternalType( 'array', $article->reviews );
198 |
199 | $review = array_shift( $article->reviews );
200 | $this->assertInstanceOf( '\Model\Review', $review );
201 |
202 | }
203 |
204 | public function testJoinManyWithLimitedFields()
205 | {
206 | $article = $this->_getArticle();
207 | $article->reviews = $this->_reviewIds;
208 | $this->_insertReviews();
209 |
210 | $article->joinMany( 'reviews', '\Model\Review', 'reviews', array( 'title' ) );
211 |
212 | $this->assertInternalType( 'array', $article->reviews );
213 |
214 | $review = array_shift( $article->reviews );
215 | $this->assertInstanceOf( '\Model\Review', $review );
216 | $this->assertFalse( isset( $review->text ) );
217 |
218 | }
219 |
220 | public function testDateFormat()
221 | {
222 | $article = $this->_insertArticle();
223 |
224 | $this->assertSame( date( Model::$dateFormat, $this->_timestamp ), $article->getDate( 'date' ) );
225 | $this->assertSame( date( 'Y', $this->_timestamp ), $article->getDate( 'date', 'Y' ) );
226 | }
227 |
228 | public function testTimeFormat()
229 | {
230 | $article = $this->_insertArticle();
231 |
232 | $this->assertSame( date( Model::$timeFormat, $this->_timestamp ), $article->getTime( 'date' ) );
233 | $this->assertSame( date( 'Y-m-s H:i', $this->_timestamp ), $article->getDate( 'date', 'Y-m-s H:i' ) );
234 | }
235 |
236 | public function testReturnsNullIfPropertyDoesntExist()
237 | {
238 | $o = new \Model\Note;
239 | $this->assertNull( $o->something );
240 | }
241 |
242 | protected function _getArticle()
243 | {
244 | $article = new \Model\Article;
245 | $article->fill( $this->_getArticleData() );
246 | return $article;
247 | }
248 |
249 | protected function _getAuthor()
250 | {
251 | $author = new \Model\Author;
252 | $author->fill( $this->_getAuthorData() );
253 | return $author;
254 | }
255 |
256 | protected function _getCommentData(){
257 | return array ( 'date' => new \MongoDate( 12345 ),
258 | 'text' => 'Comment text');
259 | }
260 |
261 | protected function _getAuthorData(){
262 | return array (
263 | '_id' => $this->_authorId,
264 | 'name' => 'John Doe',
265 | 'email' => 'john@mail.com');
266 | }
267 |
268 | protected function _getArticleData()
269 | {
270 | return array(
271 | '_id' => $this->_articleId,
272 | 'author_id'=> $this->_authorId,
273 | 'title' => 'Lorem',
274 | 'text' => 'Sample text',
275 | 'note' => array( 'text' => 'Note text'),
276 | 'comments' => array ( $this->_getCommentData() ),
277 | 'date' => new \MongoDate( $this->_timestamp )
278 | );
279 | }
280 |
281 | protected function _insertArticle()
282 | {
283 | $article = $this->_getArticle();
284 | self::$_dbConnection->articles->insert( $article );
285 | return $article;
286 | }
287 |
288 | protected function _insertAuthor()
289 | {
290 | $author = $this->_getAuthor();
291 | self::$_dbConnection->authors->insert( $author );
292 | return $author;
293 | }
294 |
295 | protected function _insertReviews()
296 | {
297 |
298 | $reviewsData = array(
299 | array( '_id' => $this->_reviewIds[0],
300 | 'title' => 'review1',
301 | 'text' => 'text' ),
302 | array( '_id' => $this->_reviewIds[1],
303 | 'title' => 'review2',
304 | 'text' => 'text' )
305 | );
306 |
307 | self::$_dbConnection->reviews->batchInsert( $reviewsData );
308 |
309 | }
310 |
311 | }
--------------------------------------------------------------------------------
/src/Mawelous/Yamop/Model.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class Model
10 | {
11 | /**
12 | * Collection name. Set it for each model.
13 | *
14 | * @var string
15 | */
16 | protected static $_collectionName = '';
17 |
18 | /**
19 | * Database connection name set in mapper
20 | *
21 | * @var string
22 | */
23 | protected static $_connectionName = null;
24 |
25 | /**
26 | * Specifies if created_at and updated_at fields
27 | * should be added and serve automatically
28 | * @var bool
29 | */
30 | public static $timestamps = false;
31 |
32 | /**
33 | * Information about mapper class name
34 | * @var string
35 | */
36 | protected static $_mapperClassName = 'Mawelous\Yamop\Mapper';
37 |
38 | /**
39 | * Informations about embedded object list
40 | * that will be created while this object creation.
41 | * Array key is a name of field that keeps array of objects.
42 | * Array value specifies Model class to which objects should be mapped.
43 | *
44 | *
45 | * protected static $_embeddedObjectList = array (
46 | * 'tags' => 'HackerTag',
47 | * 'notifications' => 'Notification',
48 | * );
49 | *
50 | *
51 | * @var array
52 | */
53 | protected static $_embeddedObjectList = array();
54 |
55 | /**
56 | * Informations about embedded object
57 | * that will be created while this object creation.
58 | * Array key is a name of field that keeps object.
59 | * Array value specifies Model class to which objects should be mapped.
60 | *
61 | *
62 | * protected static $_embeddedObject = array (
63 | * 'address' => 'Address',
64 | * );
65 | *
66 | *
67 | * @var array
68 | */
69 | protected static $_embeddedObject = array();
70 |
71 | /**
72 | * Format of date to return with getDate funciton
73 | *
74 | * @var string
75 | */
76 | public static $dateFormat = 'm/d/y';
77 |
78 | /**
79 | * Format of time to return with getTime cunftion
80 | * @var string
81 | */
82 | public static $timeFormat = 'm/d/y H:i';
83 |
84 | /**
85 | * Sets object variables
86 | *
87 | * @param array $array
88 | */
89 | public function __construct( $array = array() )
90 | {
91 | $this->fill( $array );
92 | }
93 |
94 | /**
95 | * Returns null if property doesn't exist
96 | */
97 | public function __get( $property )
98 | {
99 | return null;
100 | }
101 |
102 | /**
103 | * Fills object with variables.
104 | * Sets embedded objects if they are registered in
105 | * $_embeddedObjectLisr or $_embeddedObject.
106 | *
107 | * @param array $array
108 | */
109 | public function fill( $array = array() )
110 | {
111 | if ( !empty( $array ) ) {
112 | foreach ( $array as $key => $value ) {
113 | //checks if field contains embedded object list
114 | if( in_array( $key, array_keys ( static::$_embeddedObjectList ) ) && is_array( $value ) ) {
115 | $this->{$key} = array();
116 | foreach ( $value as $eKey => $eData ) {
117 | if( is_array( $eData ) ){
118 | $this->{$key}[ $eKey ] = new static::$_embeddedObjectList[ $key ] ( $eData );
119 | }
120 | }
121 | }elseif( in_array( $key, array_keys ( static::$_embeddedObject ) ) && is_array( $value ) ) {
122 | $this->{$key} = new static::$_embeddedObject[ $key ] ( $value );
123 | }else {
124 | $this->$key = $value;
125 | }
126 | }
127 | if ( !empty( $this->_id ) ) {
128 | $this->id = (string) $this->_id;
129 | }
130 | }
131 | }
132 |
133 | /**
134 | * Gets proper Mapper for object
135 | *
136 | * @param int $fetchType Fetch type as specified in Mapper constants
137 | * @return Mapper
138 | */
139 | public static function getMapper( $fetchType = null )
140 | {
141 | return new static::$_mapperClassName( get_called_class(), $fetchType );
142 | }
143 |
144 | /**
145 | * Gets colletion name for model
146 | * It should be specified in $_collectionName
147 | *
148 | * @throws \Exception
149 | * @return string
150 | */
151 | public static function getCollectionName()
152 | {
153 | if( empty( static::$_collectionName ) ){
154 | throw new \Exception( 'There\'s no collection name for ' . get_called_class() );
155 | }
156 | return static::$_collectionName;
157 | }
158 |
159 | /**
160 | * Gets database connection name for model
161 | * It should be specified in $_databaseConnection
162 | *
163 | * @return string
164 | */
165 | public static function getConnectionName()
166 | {
167 | return static::$_connectionName;
168 | }
169 |
170 | /**
171 | * Saves object to database.
172 | * Adds timestamps if wanted in $timestamps.
173 | *
174 | * @return mixed MongoCollection save result
175 | */
176 | public function save()
177 | {
178 | if ( static::$timestamps ) {
179 | if ( !$this->hasMongoId() ) {
180 | $this->created_at = new \MongoDate();
181 | }
182 | $this->updated_at = new \MongoDate();
183 | }
184 |
185 | if( ! $this->hasMongoId() ){
186 | $this->_id = new \MongoId();
187 | }
188 |
189 | unset( $this->id );
190 |
191 | //deleteng string ids in embedded objects
192 | foreach ( static::$_embeddedObject as $fieldName => $embeddedObject ){
193 | if( isset( $this->$fieldName ) && !empty ( $this->$fieldName ) ){
194 | unset ( $embeddedObject->id );
195 | }
196 | }
197 |
198 | foreach ( static::$_embeddedObjectList as $fieldName => $class ){
199 | if( isset( $this->$fieldName ) && !empty ( $this->$fieldName ) ){
200 | foreach( $this->$fieldName as $embeddedObject ){
201 | unset ( $embeddedObject->id );
202 | }
203 | }
204 | }
205 |
206 | $result = static::getMapper()->save( get_object_vars( $this ) );
207 | $this->id = (string) $this->_id;
208 | return $result;
209 |
210 | }
211 |
212 | /**
213 | * Removes object form database, totally.
214 | *
215 | * @return bool|array MongoCollection remove result
216 | */
217 | public function remove()
218 | {
219 | return static::getMapper()->remove( array( '_id' => $this->_id ) );
220 | }
221 |
222 | /**
223 | * Gets object by its id.
224 | *
225 | * @param string|MongoId $id Can be passed as string or MongoId
226 | * @return Model
227 | */
228 | public static function findById( $id )
229 | {
230 | return static::getMapper()->findById( $id );
231 | }
232 |
233 | /**
234 | * Gets object by query.
235 | * Refferer to Mapper's findOne
236 | *
237 | * @param array $query Query as for findOne in mongodb driver
238 | * @param array $fields
239 | * @return Model
240 | */
241 | public static function findOne( $query = array(), $fields = array() )
242 | {
243 | return static::getMapper()->findOne( $query, $fields );
244 | }
245 |
246 | /**
247 | * Refferer to Mapper's find.
248 | * Gets Mapper object with cursor set.
249 | *
250 | * @param array $query Query as for find in mongodb driver
251 | * @param array $fields
252 | * @return Model
253 | */
254 | public static function find( $query = array(), $fields = array() )
255 | {
256 | return static::getMapper()->find( $query, $fields );
257 | }
258 |
259 | /**
260 | * Checks if _id is set in object
261 | * @return boolean
262 | */
263 | public function hasMongoId()
264 | {
265 | return isset( $this->_id ) && !empty( $this->_id );
266 | }
267 |
268 | /**
269 | * Return date as string - converts MongoDate
270 | *
271 | * @param string $field Field name that keeps date
272 | * @param string $format Format for date function
273 | * @return srting
274 | */
275 | public function getDate( $field, $format = null )
276 | {
277 | $format = $format == null ? static::$dateFormat : $format;
278 | return isset($this->$field->sec) ? date( $format, $this->$field->sec ) : null;
279 | }
280 |
281 | /**
282 | * Return time as string - converts MongoDate
283 | *
284 | * @param string $field Field name that keeps time
285 | * @param string $format Format for date function
286 | * @return srting
287 | */
288 | public function getTime( $field, $format = null )
289 | {
290 | $format = $format == null ? static::$timeFormat : $format;
291 | return isset($this->$field->sec) ? date( $format, $this->$field->sec ) : null;
292 | }
293 |
294 | /**
295 | * Sets id (both string and MongoId version)
296 | * @param string|MongoId $id
297 | */
298 | public function setId($id)
299 | {
300 | if(!$id instanceof \MongoId){
301 | $id = new \MongoId($id);
302 | }
303 |
304 | $this->_id = $id;
305 | $this->id = (string) $id;
306 | }
307 |
308 | /**
309 | * Perform join like operation to many objects.
310 | * Allows to get all object related to this one
311 | * by array of mongo ids that is kept in $variable
312 | *
313 | * As simple as that
314 | *
315 | * $user->joinMany( 'posts', 'Post' );
316 | *
317 | *
318 | * @param string $variable Field keeping array of mongo ids
319 | * @param string $class Model class name of joined objects
320 | * @param string $toVariable Variable that should keep new results, same if null given.
321 | * @param array $fields Fields to return if you want to limit
322 | * @return Model
323 | */
324 | public function joinMany( $variable, $class, $toVariable = null, $fields = array() )
325 | {
326 | if( isset( $this->$variable ) && is_array( $this->$variable ) && !empty( $this->$variable ) ){
327 | if( empty( $toVariable ) ){
328 | $toVariable = $variable;
329 | }
330 | $this->$toVariable = $class::getMapper()->find( array ('_id' => array ('$in' => $this->$variable) ), $fields )->get();
331 | }
332 | return $this;
333 | }
334 |
335 | /**
336 | * Perform join like operation to one object.
337 | * Allows to get object related to this one
338 | * by MongoId that is kept in $variable
339 | *
340 | * As simple as that
341 | *
342 | * $user->joinOne( 'article', 'Article' );
343 | *
344 | *
345 | * @param string $variable Field keeping MongoId of object you want to join
346 | * @param string $class Model class name of joined object
347 | * @param string $toVariable Variable that should keep new result, same if null given.
348 | * @param array $fields Fields to return if you want to limit
349 | * @return Model
350 | */
351 | public function joinOne( $variable, $class, $toVariable = null, $fields = array() )
352 | {
353 | if( isset($this->$variable) && !empty( $this->$variable ) ){
354 | if( empty( $toVariable ) ){
355 | $toVariable = $variable;
356 | }
357 | $this->$toVariable = $class::getMapper()->findOne( array ('_id' => $this->$variable ), $fields );
358 | }
359 | return $this;
360 | }
361 |
362 | }
363 |
--------------------------------------------------------------------------------
/src/Mawelous/Yamop/Mapper.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | */
11 | class Mapper
12 | {
13 |
14 | /**
15 | * Fetching as model and array of models
16 | * with keys containing string id
17 | *
18 | * @var int
19 | */
20 | const FETCH_OBJECT = 1;
21 |
22 | /**
23 | * Fetching as array and array of array
24 | * with keys containing string id
25 | *
26 | * @var int
27 | */
28 | const FETCH_ARRAY = 2;
29 |
30 | /**
31 | * Fetching as JSON
32 | *
33 | * @var int
34 | */
35 | const FETCH_JSON = 3;
36 |
37 | /**
38 | * Fetching as array and array of array
39 | * with numeric keys
40 | *
41 | * @var int
42 | */
43 | const FETCH_NUMERIC_ARRAY = 4;
44 |
45 | /**
46 | * Fetching as model and array of models
47 | * with numeric keys
48 | *
49 | * @var int
50 | */
51 | const FETCH_OBJECT_NO_KEYS = 5;
52 |
53 | /**
54 | * Mongo connection to database
55 | *
56 | * @var MongoDB
57 | */
58 | protected static $_database;
59 |
60 | /**
61 | * Type of fetch method, constans defined in this class
62 | *
63 | * @var int
64 | */
65 | protected $_fetchType = self::FETCH_OBJECT;
66 |
67 | /**
68 | * Class to return
69 | *
70 | * @var string
71 | */
72 | protected $_modelClassName;
73 |
74 | /**
75 | * MongoCursor returned with find
76 | *
77 | * @var \MongoCursor
78 | */
79 | protected $_cursor;
80 |
81 | /**
82 | * Keeps information about join like operations to perform
83 | *
84 | * @var array
85 | */
86 | protected $_joins = array();
87 |
88 | /**
89 | * Sest model class name and fetch type
90 | *
91 | * @param string $modelClass
92 | * @param int $fetchType One of constants
93 | * @throws \Exception
94 | */
95 | public function __construct( $modelClass = null, $fetchType = null)
96 | {
97 | if( !empty( $modelClass ) ){
98 | $this->_modelClassName = $modelClass;
99 | } elseif ( empty( $this->_modelClassName ) ) {
100 | throw new \Exception( 'Mapper needs to know model class.' );
101 | }
102 |
103 | if ( empty(static::$_database) ) {
104 | throw new \Exception( 'Give me some database. You can pass it with setDatabase function.' );
105 | }
106 |
107 | if ( !empty($fetchType) ) {
108 | $this->setFetchType( $fetchType );
109 | }
110 | }
111 |
112 | /**
113 | * Allows to call standard MongoCollection functions
114 | * like count, update
115 | *
116 | * @param string $functionName
117 | * @param array $functionArguments
118 | */
119 | public function __call( $functionName, $functionArguments )
120 | {
121 | return call_user_func_array(
122 | array( $this->_getModelCollection(), $functionName ),
123 | $functionArguments
124 | );
125 | }
126 |
127 | /**
128 | * Acts exactly like MongoCollection find but
129 | * sets it result to $_cursor and returns this object
130 | *
131 | * @param array $query
132 | * @param array $fields
133 | * @return Mapper
134 | */
135 | public function find( $query = array(), $fields = array() )
136 | {
137 | $this->_cursor = $this->_getModelCollection()->find( $query, $fields );
138 | return $this;
139 | }
140 |
141 | /**
142 | * Acts like MongoCollection function but returns
143 | * result of expected type
144 | *
145 | * @param array $query
146 | * @param array $fields
147 | * @return array|string|Model
148 | */
149 | public function findOne( $query = array(), $fields = array() )
150 | {
151 | $result = $this->_getModelCollection()->findOne( $query, $fields );
152 | return $this->_fetchOne( $result );
153 | }
154 |
155 | /**
156 | * Gets document by its id
157 | *
158 | * @param string|MongoId $id
159 | * @return array|string|Model
160 | */
161 | public function findById( $id )
162 | {
163 | if ( !$id instanceof \MongoId ) {
164 | $id = new \MongoId( $id );
165 | }
166 | return $this->findOne( array( '_id' => $id ) );
167 | }
168 |
169 | /**
170 | * Updates an object and returns it
171 | *
172 | * @param array $query
173 | * @param array $update
174 | * @param array $fields
175 | * @param array $options
176 | * @return array|string|Model
177 | */
178 | public function findAndModify( $query, $update = array(), $fields = array(), $options = array() )
179 | {
180 | $result = $this->_getModelCollection()->findAndModify( $query, $update, $fields, $options );
181 | return $this->_fetchOne( $result );
182 | }
183 |
184 | /**
185 | * Fetches result as object
186 | *
187 | * @param array $result
188 | * @return Model|null
189 | */
190 | public function fetchObject( $result )
191 | {
192 | return is_array( $result ) ? new $this->_modelClassName( $result ) : null;
193 | }
194 |
195 | /**
196 | * Performs sort on MongoCursor
197 | *
198 | * @param array $array
199 | * @return Mapper
200 | */
201 | public function sort( $array )
202 | {
203 | $this->_checkCursor();
204 | $this->_cursor->sort( $array );
205 | return $this;
206 | }
207 |
208 | /**
209 | * Performs limit on MongoCursor
210 | *
211 | * @param int $num
212 | * @return Mapper
213 | */
214 | public function limit( $num )
215 | {
216 | $this->_checkCursor();
217 | $this->_cursor->limit( $num );
218 | return $this;
219 | }
220 |
221 | /**
222 | * Performs skip on MongoCursor
223 | *
224 | * @param int $num
225 | * @return Mapper
226 | */
227 | public function skip( $num )
228 | {
229 | $this->_checkCursor();
230 | $this->_cursor->skip( $num );
231 | return $this;
232 | }
233 |
234 | /**
235 | * Writes informations about joins
236 | *
237 | * @param string $variable Field name in database that keeps MongoId of other object
238 | * @param string $class Model name which should be created
239 | * @param string $toVariable Name od variable to which it should be writen
240 | * @return Mapper
241 | */
242 | public function join( $variable, $class, $toVariable = null, $fields = array() )
243 | {
244 | $this->_joins[] = array ( 'variable' => $variable,
245 | 'class' => $class,
246 | 'to_variable' => $toVariable,
247 | 'fields' => $fields
248 | );
249 | return $this;
250 | }
251 |
252 | /**
253 | * Performs proper skip and limit to get
254 | * data package that can be wraped in paginator
255 | *
256 | * @param int $perPage
257 | * @param int $page
258 | * @param mixed $options Options you want to pass
259 | */
260 | public function getPaginator( $perPage = 10, $page = 1, $options = null)
261 | {
262 | $this->_checkCursor();
263 | $total = $this->_cursor->count();
264 | $this->_cursor->skip( ( $page -1 ) * $perPage )->limit( $perPage );
265 | $result = $this->get();
266 | return $this->_createPaginator($result, $total, $perPage, $page, $options);
267 | }
268 |
269 |
270 | /**
271 | * Returns the data
272 | *
273 | * @result array|string Array of arrays or json string
274 | */
275 | public function get()
276 | {
277 | $this->_checkCursor();
278 |
279 | switch ( $this->_fetchType ) {
280 |
281 | case self::FETCH_ARRAY:
282 | $this->_checkCursor();
283 | return $this->_performJoins( iterator_to_array( $this->_cursor ) );
284 | break;
285 |
286 | case self::FETCH_NUMERIC_ARRAY:
287 | $this->_checkCursor();
288 | return $this->_performJoins( iterator_to_array( $this->_cursor, false ) );
289 | break;
290 |
291 | case self::FETCH_JSON:
292 | $this->_checkCursor();
293 | return json_encode($this->_performJoins( iterator_to_array( $this->_cursor, false ) ));
294 | break;
295 |
296 | case self::FETCH_OBJECT_NO_KEYS:
297 | $result = array();
298 | foreach ( $this->_cursor as $key => $item ) {
299 | $result[] = $this->fetchObject( $item );
300 | }
301 | return $this->_performJoins($result);
302 | break;
303 |
304 | default:
305 | $result = array();
306 | foreach ( $this->_cursor as $key => $item ) {
307 | $result[$key] = $this->fetchObject( $item );
308 | }
309 | return $this->_performJoins($result);
310 | break;
311 | }
312 | }
313 |
314 | /**
315 | * Gets data as array
316 | *
317 | * @para bool $idKeys Should keys contain MongoId of object as string
318 | * @return array
319 | */
320 | public function getArray( $idKeys = true )
321 | {
322 | $this->_fetchType = $idKeys ? self::FETCH_ARRAY : self::FETCH_NUMERIC_ARRAY;
323 | return $this->get();
324 | }
325 |
326 | /**
327 | * Gets data as json
328 | *
329 | * @return string
330 | */
331 | public function getJson()
332 | {
333 | $this->_fetchType = self::FETCH_JSON;
334 | return $this->get();
335 | }
336 |
337 | /**
338 | * Gets MongoCursor
339 | *
340 | * @return \MongoCursor
341 | */
342 | public function getCursor()
343 | {
344 | return $this->_cursor;
345 | }
346 |
347 | /**
348 | * Sets fetch type
349 | *
350 | * @param int $fetchType Use one of contsnts
351 | * @throws \Exception
352 | * @return Mapper
353 | */
354 | public function setFetchType( $fetchType )
355 | {
356 | if( !in_array( $fetchType,
357 | array( self::FETCH_OBJECT,
358 | self::FETCH_JSON,
359 | self::FETCH_ARRAY,
360 | self::FETCH_NUMERIC_ARRAY,
361 | self::FETCH_OBJECT_NO_KEYS ) )
362 | ){
363 | throw new \Exception( 'Please use of of provided methotd for fetch' );
364 | }
365 |
366 | $this->_fetchType = $fetchType;
367 |
368 | return $this;
369 | }
370 |
371 | /**
372 | * Sets database. That needs to be performed before you can get any data.
373 | *
374 | * @param MongoDB|array $database
375 | * @return void
376 | */
377 | public static function setDatabase( $database )
378 | {
379 | if( $database instanceof \MongoDb ){
380 | static::$_database = array( 'defalut' => $database );
381 | } elseif( is_array( $database ) ){
382 | static::$_database = $database;
383 | } else {
384 | throw new \Exception('Database must be an array fo MongoDb objects or MongoDb object');
385 | }
386 | }
387 |
388 | /**
389 | * Gets database connections
390 | *
391 | * @return \MongoDB
392 | */
393 | public static function getDatabase()
394 | {
395 | return self::$_database;
396 | }
397 |
398 | /**
399 | * Get connection to collection for mapper's model
400 | */
401 | protected function _getModelCollection()
402 | {
403 | $modelClass = $this->_modelClassName;
404 | $collectionName = $modelClass::getCollectionName();
405 | $connectionName = $modelClass::getConnectionName();
406 | if( !empty( $connectionName ) ){
407 | $database = self::$_database[ $connectionName ];
408 | } else {
409 | $database = reset( self::$_database );
410 | }
411 | return $database->$collectionName;
412 | }
413 |
414 | /**
415 | * Converts document to proper type
416 | *
417 | * @param array $array Document
418 | * @return array|string|Model
419 | */
420 | protected function _fetchOne( $array )
421 | {
422 | switch ( $this->_fetchType ) {
423 | case self::FETCH_ARRAY:
424 | return $array;
425 | break;
426 |
427 | case self::FETCH_JSON:
428 | return json_encode( $array );
429 | break;
430 |
431 | default:
432 | return $this->fetchObject( $array );
433 | break;
434 | }
435 | }
436 |
437 | /**
438 | * Used to create your own paginator.
439 | * Needs to be implemented if you want to use getPaginator function.
440 | *
441 | * @param mixed $results
442 | * @param int $totalCount
443 | * @param int $perPage
444 | * @param int $page
445 | * @param mixed $options
446 | * @throws \Exception
447 | */
448 | protected function _createPaginator( $results, $totalCount, $perPage, $page, $options )
449 | {
450 | throw new \Exception('If you want to get paginator '.
451 | 'please extend mapper class implementing _createPaginator function. '.
452 | 'You have a set of params. Return whatever you want.');
453 | }
454 |
455 | /**
456 | * Checks if cursor already exists so you can perform operations on it.
457 | *
458 | * @throws \Exception
459 | */
460 | protected function _checkCursor()
461 | {
462 | if ( empty( $this->_cursor ) ) {
463 | throw new \Exception( 'There is no cursor, so you can not get anything' );
464 | }
465 | }
466 |
467 | /**
468 | * Magically performs join like operations registered with join function.
469 | * Allows to connect every document in cursor to the other by MongoId
470 | * kept as variable in base document.
471 | *
472 | * @param array $array
473 | * @throws \Exception
474 | * @return array
475 | */
476 | protected function _performJoins( array $array )
477 | {
478 | foreach ( $this->_joins as $join ){
479 |
480 | reset( $array );
481 |
482 | $toVariable = !empty( $join['to_variable'] ) ? $join['to_variable'] : $join['variable'];
483 | $variable = $join['variable'];
484 | $class = $join['class'];
485 | $fields = $join['fields'];
486 |
487 | $ids = array();
488 | if( !empty ( $array ) && is_object( current ( $array ) )){
489 | foreach ( $array as $item ){
490 | if( isset( $item->$variable ) && ( $item->$variable instanceof \MongoId ) ){
491 | $ids[] = $item->$variable;
492 | }
493 | }
494 | if( count( $ids ) ){
495 | $joined = $class::getMapper( Mapper::FETCH_OBJECT )->find( array ( '_id' => array ('$in' => $ids) ), $fields )->get();
496 | foreach ( $array as $item ){
497 | if( isset( $item->$variable ) && ( $item->$variable instanceof \MongoId ) ){
498 | $item->$toVariable = $joined[ (string) $item->$variable ];
499 | }
500 | }
501 | }
502 | } elseif ( !empty ( $array ) ){
503 | foreach ( $array as $item ){
504 | if( isset( $item[ $variable ] ) && ( $item[ $variable ] instanceof \MongoId ) ){
505 | $ids[] = $item[ $variable ];
506 | }
507 | }
508 | if( count( $ids ) ){
509 | $joined = $class::getMapper()->find( array ( '_id' => array ('$in' => $ids) ), $fields )->getArray();
510 | foreach ( $array as &$item ){
511 | if( isset( $item[ $variable ] ) && ( $item[ $variable ] instanceof \MongoId ) ){
512 | $item[$toVariable] = $joined[ (string) $item[ $variable ] ];
513 | }
514 | }
515 | }
516 | }
517 | }
518 | return $array;
519 | }
520 |
521 | }
522 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yamop
2 | ### Yet another MongoDB ODM for PHP
3 |
4 | - [What's that?](#whatsthat)
5 | - [Requirements](#requirements)
6 | - [Installation](#installation)
7 | - [Usage](#usage)
8 | - [getting](#getting)
9 | - [save, update, delete](#save)
10 | - [embedded objects](#embedded)
11 | - [relations](#related)
12 | - [output format](#output)
13 | - [pagination](#pagination)
14 | - [timestamps](#timestamps)
15 | - [dates and time](#datetime)
16 | - [transactions](#transactions)
17 | - [advanced](#advanced)
18 | - [multiple connections](#multiple_connections)
19 | - [Issues](#issues)
20 | - [License](#license)
21 |
22 |
23 | ## What's that?
24 | This is yet another, open source, and very simple ODM for [MongoDB](http://www.mongodb.org/).
25 | It works like the standard MongoDB PHP extension interface but returns objects instead of arrays (as ODM). Queries stay the same.
26 | One of its coolest features are joins which allow you to query for related objects.
27 |
28 | List of features:
29 |
30 | - [String IDs](#stringid) (easier linking in views)
31 | - [Embedded objects](#embedded)
32 | - [Related objects](#related) (performing "join like" operations)
33 | - [JSON format](#output)
34 | - [Paginator](#pagination)
35 | - [Timestamps](#timestamps) (created_at and updated_at fields added on demand)
36 | - [Printing date and time](#datetime)
37 | - [Transactions](#transactions) (PHP error support only)
38 |
39 |
40 | ## Requirements
41 | + PHP 5.3+
42 | + PHP MongoDB Extension
43 |
44 |
45 | ## Installation
46 |
47 | You can simply download it [here](https://github.com/mawelous/yamop) or use [Composer](http://getcomposer.org/).
48 |
49 | In the `require` key inside the `composer.json` file add the following
50 |
51 | ```yml
52 | "mawelous/yamop": "dev-master"
53 | ```
54 |
55 | Save it and run the Composer update command
56 |
57 | $ composer update
58 |
59 | After Composer is done you only need to add the following lines to your code
60 |
61 | ```php
62 | $connection = new \MongoClient( 'your_host' );
63 | \Mawelous\Yamop\Mapper::setDatabase( $connection->your_db_name );
64 | ```
65 |
66 | You can pass any `MongoDB` instance to the `setDatabase` function.
67 |
68 | Now extend `Mawelous\Yamop\Model` from within any of your models:
69 |
70 | ```php
71 | class User extends \Mawelous\Yamop\Model
72 | {
73 | protected static $_collectionName = 'users';
74 | }
75 | ```
76 |
77 | That's it!
78 |
79 |
80 | ##Usage
81 |
82 | Each object has an `_id`, which is a `MongoId`, and an `id` key which is its string representation.
83 |
84 | Every document in `MongoDB` is returned as an object, every key is a property - here a sample document inside `MongoDB`
85 |
86 | ```json
87 | {
88 | "_id": ObjectId("51b6ea4fb7846c9410000001"),
89 | "name": "John Doe",
90 | "birthdate": ISODate("2013-05-25T12:15:25.0Z"),
91 | "email": "john@something.com"
92 | }
93 | ```
94 | The document above would be represented in PHP as follows:
95 |
96 | ```php
97 | object(User)[44]
98 | public '_id' =>
99 | object(MongoId)[46]
100 | public '$id' => string '51b6ea4fb7846c9410000001' (length=24)
101 | public 'name' => string 'John Doe' (length=8)
102 | public 'birthdate' =>
103 | object(MongoDate)[47]
104 | public 'sec' => int 1369484125
105 | public 'usec' => int 0
106 | public 'email' => string 'john@something.com' (length=18)
107 | public 'id' => string '51b6ea4fb7846c9410000001' (length=24)
108 | ```
109 | There are two possibilities to pass properties to object
110 |
111 | ```php
112 | // properties as array
113 | $user = new User( array( 'name' => 'John', 'email' => 'email@email.com' ) );
114 |
115 | // or each property separately
116 | $user = new User;
117 | $user->name = 'John';
118 | $user->emial = 'email@email.com';
119 | ```
120 |
121 | After version 0.2.1 (excluding it) Yamop returns `null` when you try to get a property that doesn't exist.
122 |
123 |
124 | ### Getting data
125 | Want to get a document by its id? There is a simple way.
126 |
127 |
128 | ```php
129 | $user = User::findById( '51a61930b7846c400f000002' )
130 | //or
131 | $mongoId = new MongoId( '51a61930b7846c400f000002' );
132 | $user = User::findById( $mongoId )
133 | ```
134 |
135 | Getting one document by query is simple too. Method `findOne` works exactly like native [`findOne`](#http://php.net/manual/en/mongocollection.findone.php) but it returns an object. As second parameter you can pass an array of fields. This means the parameters and queries stay the same, which is pretty great!
136 |
137 | ```php
138 | $user = User::findOne( array( 'email' => 'user@mail.com' ) );
139 | //or
140 | $user = User::findOne( array( 'email' => 'user@mail.com' ), array( 'email', 'username', 'birthdate' ) );
141 | ```
142 |
143 | #### Introducing Mapper
144 | There is a `Mapper` class in Yamop which is responsible for retrieving data. I separated it from `Model` so it can stay as data container. If you want to create more complicated queries you want to use the mapper. You can get it by using the `getMapper` method or creating new instance of it passing model class as string.
145 |
146 | ```php
147 | //first possibility
148 | $mapper = User::getMapper();
149 | //second possibility
150 | $mapper = new Mawelous\Yamop\Mapper( 'User' );
151 | ```
152 |
153 | #### Find methods
154 | `findOne` introduced before for `Model` is `Mapper's` method. `Model` just refers to it. You could call it like this
155 |
156 | ```php
157 | //findOne with Mapper
158 | $user = User::getMapper()->findOne( array( 'email' => 'user@mail.com' ) );
159 | ```
160 |
161 | There is a `find` method that gets more then one document. It also works like native [`find`](#http://www.php.net/manual/en/mongocollection.find.php) but it returns a `Mapper`. You can then perform other operations on it like `sort`, `limit`, `skip` which all work like native as well.
162 | To get result as array of objects use `get` method.
163 |
164 | ```php
165 | //You can call it directly with Model
166 | $messages = Message::find( array( 'to_id' => new MongoId( $stringId ), 'to_status' => Message::STATUS_UNREAD ) )
167 | ->sort( array( 'created_at' => -1 ) )
168 | ->limit( 10 )
169 | ->get();
170 |
171 | //or using Mapper itself
172 | $messages = Message::getMapper()
173 | ->find( array( 'to_id' => new MongoId( $stringId ), 'to_status' => Message::STATUS_UNREAD ) )
174 | ->sort( array( 'created_at' => -1 ) )
175 | ->limit( 10 )
176 | ->get();
177 | ```
178 |
179 | `findAndModify` is equivalent to native [`findAndModify`](#http://www.php.net/manual/en/mongocollection.findandmodify.php) but serves objects.
180 |
181 |
182 | ### Save, Update and Delete
183 | `save` method is used to create and update objects. That's the code to create new object and write it to the database
184 |
185 | ```php
186 | $user = new User( array( 'name' => 'John', 'email' => 'email@email.com' ) );
187 | $user->save();
188 | ```
189 | You can get `_id` of newly created object just after `save`.
190 |
191 | Deleting is simple
192 |
193 | ```php
194 | $user->remove();
195 | ```
196 |
197 | Those methods return the same results as the native `remove` and `save` methods. If you want to update multiple documents use the native function like [here](#multiple-update).
198 |
199 | ### Extending Mapper
200 | You can extend `Mapper` if you want to add more methods. For example I created UserMapper which has a method that posts a message on an user's Facebook wall. Just let `Mapper` know which `Model` class to use.
201 |
202 | ```php
203 | class UserMapper extends Mawelous\Yamop\Mapper
204 | {
205 | protected $_modelClassName = 'User';
206 |
207 | public function findActiveUsers( $limit = 10, $sort = 'birthdate' )
208 | {
209 | //method code
210 | }
211 | }
212 | ```
213 |
214 | If you want to register a different `Mapper` for a model just declare it in the model
215 |
216 | ```php
217 | class User extends Model
218 | {
219 | ...
220 | protected static $_mapperClassName = 'UserMapper';
221 | ...
222 | ```
223 |
224 | Now you just execute the `Mapper`
225 |
226 | ```php
227 | $mapper = User::getMapper();
228 | //and then
229 | $mapper->findActiveUsers( 5 );
230 | ```
231 |
232 | This will return an instance of UserMapper. You can also just create a new mapper
233 |
234 | ```php
235 | $userMapper = new UserMapper;
236 | //and then
237 | $userMapper->findActiveUsers( 5 );
238 | ```
239 |
240 |
241 | ### Count, Indexes, multi update and others
242 |
243 | All methods called on `Mapper` that are not present are passed to the original [`MongoCollection`](#http://php.net/manual/en/class.mongocollection.php). So you can use `update`, `count`, `batchInsert`, `ensureIndex` and even `drop` directly with the native methods.
244 |
245 | ```php
246 | //count
247 | Message::getMapper()->count( array( 'to_id' => $userId, 'to_status' => Message::STATUS_UNREAD ) );
248 | //update
249 | Contest::getMapper()->update(
250 | array('status' => Contest::STATUS_READY_DRAFT,
251 | 'start_date' => array ('$lte' => new MongoDate(strtotime('midnight')) )),
252 | array('$set' => array( 'status' => Contest::STATUS_ACTIVE) ),
253 | array('multiple' => true)
254 | );
255 | //drop
256 | Contest::getMapper()->drop();
257 | ```
258 |
259 |
260 | ### Embedded objects
261 |
262 | Do you have more objects within the current object? Yamop will convert it automatically. Just let it know.
263 |
264 | ```php
265 | class User extends \Mawelous\Yamop\Model
266 | {
267 | protected static $_collectionName = 'users';
268 |
269 | // One Address object embedded in address property
270 | protected static $_embeddedObject = array (
271 | 'address' => 'Address',
272 | );
273 | // Many Notification objects embedded in array that is kept ass notifications
274 | protected static $_embeddedObjectList = array (
275 | 'notifications' => 'Notification',
276 | );
277 | }
278 | ```
279 | Then it will convert object embedded in `address` field to `Address` PHP object and `notifications` array of objects to array of `Notification` PHP objects. All embedded objects can be pure models - they can only extend `\Mawelous\Yamop\Model`.
280 |
281 |
282 |
283 | ### Related objects
284 |
285 | If there are relations between objects (there are sometimes) and you would like to "join" them, it's simpler than you would expect, even with `MongoDB`. All you need is to keep the `MongoId` of the related object within your base object.
286 |
287 | You don't have to register it anywhere. In my opinion it's better to do this explicit and avoid queries in background.
288 |
289 | Here's the magic:
290 |
291 | #### One
292 |
293 | The `joinOne` method in every `Model` takes three parameters. First is the name of the property which keeps the `MongoId` of the related object, second is the related objects class, and third, optional, is the property name it will be joined at.
294 |
295 | ```php
296 | // contest_id property holds MongoId of related Contest object
297 | $user = User::findById( new MongoId( $stringId ) )->joinOne( 'contest_id', 'Contest', 'contest')
298 | // and there it is
299 | $contest = $user->contest;
300 | ```
301 |
302 | #### Many
303 |
304 | The `joinMany` method in every `Model` has also three parameters. First is the name of the property which keeps an array of `MongoId`, second is the related objects class, and third, optional, is the property name it will be joined at.
305 |
306 | ```php
307 | // contests field is array of MongoIds
308 | $user = User::findById( new MongoId( $stringId ) )->joinMany( 'contests', 'Contest', 'contests')
309 | // and you have array of contests there
310 | $contests = $user->contests;
311 | ```
312 |
313 | If you want to join items to a list of items use `join` in a `Mapper`. Three parameters as in `joinOne`.
314 |
315 | ```php
316 | $commentsList = Comment::getMapper()
317 | ->find( array( 'contest_id' => new MongoId( $contestId ) ) )
318 | ->join( 'user_id', 'User', 'author' )
319 | ->limit( 10 )
320 | ->get();
321 | ```
322 |
323 |
324 | ### Output format
325 |
326 | Default fetching mode converts arrays to objects but you can also get array or JSON with `getArray` and `getJson`.
327 | As default `getArray` returns array with keys holding `MongoId` as string. If you want to receive numeric array call it with false param `getArray(false)`
328 |
329 | ```php
330 | //first possibility
331 | Comment::getMapper()
332 | ->find( array( 'contest_id' => new MongoId( $contestId ) ) )
333 | ->getArray();
334 |
335 | Comment::getMapper()
336 | ->find( array( 'contest_id' => new MongoId( $contestId ) ) )
337 | ->getJson();
338 |
339 | /* second possibility
340 | four fetch types as constants in Mapper
341 | FETCH_OBJECT
342 | FETCH_ARRAY
343 | FETCH_NUMERIC_ARRAY
344 | FETCH_JSON
345 | */
346 | Comment::getMapper( \Mawelous\Yamop\Mapper::FETCH_JSON )
347 | ->find( array( 'contest_id' => new MongoId( $contestId ) ) )
348 | ->get();
349 |
350 | /* third possibility */
351 | Comment::getMapper()
352 | ->setFetchType(\Mawelous\Yamop\Mapper::FETCH_JSON )
353 | ->find( array( 'contest_id' => new MongoId( $contestId ) ) )
354 | ->get();
355 | ```
356 |
357 | You can also get the native `MongoCursor` by calling the `getCursor` method.
358 |
359 |
360 | ### Pagination
361 |
362 | Yamop supports pagination with a little help from you. It has a `getPaginator` method which has three parameters. First is the amount of items per page, second is the current page number, and the third is a variable which you can pass to your paginator. All three are optional.
363 |
364 | ```php
365 | User::getMapper()
366 | ->find( 'status' => array ( '$ne' => User::STATUS_DELETED )) )
367 | ->sort( array( $field => $direction ) )
368 | ->getPaginator( $perPage, $page, $options );
369 | ```
370 |
371 | Your framework probably has its own paginator. Before you use the `getPaginator` method you have to implement the `_createPaginator` method in a mapper that extends `Mawelous\Yamop\Mapper`.
372 |
373 | [Laravel](http://laravel.com) would be extended like this:
374 |
375 | ```php
376 |
388 | ### Timestamps
389 |
390 | It's common to have a `created_at` and `updated_at` key in our objects. If you want to have them be set automatically for your `Model`, just declare it:
391 |
392 | ```php
393 | class User extends Model
394 | {
395 | ...
396 | public static $timestamps = true;
397 | ....
398 | ```
399 |
400 |
401 | ### Printing date and time
402 |
403 | Whether you have a timestamp or not, you might still like to print the date or time. It's recommend to keep dates as `MongoDate` this way you can echo it with `getTime` or `getDate` which takes two parameters. First is the `MongoDate` property name, second is a string that represents format passed to the PHP `date` function:
404 |
405 | ```php
406 | //date as string
407 | $user->getDate( 'birthdate', 'Y/m/d' );
408 | //time as string
409 | $user->getTime( 'created_at', 'Y/m/d H:i');
410 | //time as string using default format set in $dateFormat
411 | $user->getTime( 'created_at' );
412 | ```
413 |
414 | `Mawelous\Yamop\Model` has its default date format defined in the public static `$dateFormat` property and a time format in `$timeFormat`. You can override it if you like.
415 |
416 |
417 | ### Transactions
418 |
419 | **EXPERIMENTAL!** - It's an addition to Yamop which works independently. It doesn't support a [two phase commit](#http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/) but at least it can revert changes.
420 |
421 | That's what `Mawelous\Yamop\Transaction` is for. First you have to handle errors and run the `rollback` method within it.
422 |
423 | Similar to this:
424 |
425 | ```php
426 | set_error_handler( function($code, $error, $file, $line) {
427 | Transaction::rollback();
428 | require_once path('sys').'error'.EXT;
429 | Laravel\Error::native($code, $error, $file, $line);
430 | });
431 | ```
432 |
433 | Then you can start using the `add` method. With `add` you add code to revert changes you made with save or update. You can use a closure to do that.
434 |
435 | Here an example:
436 |
437 | ```php
438 | User::getMapper()->update(
439 | array('_id' => array ( '$in' => $userIds )),
440 | array('$inc' => array ('active_contests' => -1 )),
441 | array('multiple' => true)
442 | );
443 |
444 | Transaction::add( function () use ( $userIds ) {
445 | User::getMapper()->update(
446 | array('_id' => array ( '$in' => $userIds )),
447 | array('$inc' => array ('active_contests' => 1 )),
448 | array('multiple' => true)
449 | );
450 | });
451 | ```
452 | Now when error happens `rollback` will invoke all added methods.
453 |
454 |
455 | ### Advanced
456 |
457 |
458 | #### Multiple connections
459 | It's simple to have different connections for models. `setDatabase` can take `MongoDb` or array of `MongoDbs` as param. Keys in array are connection names.
460 | ```php
461 | $db1 = ( new \MongoClient( 'your_first_host' ) )->first_db;
462 | $db2 = ( new \MongoClient( 'your_second_host' ) )->second_db;
463 | \Mawelous\Yamop\Mapper::setDatabase( array( 'default' => $db1, 'special' => $db2 ) );
464 | ```
465 | This is how you specify connection name in model.
466 | ```php
467 | protected static $_connectionName = 'special';
468 | ```
469 | If it's not specified model will use first connection.
470 |
471 |
472 | ## Issues
473 |
474 | Any issues or questions please [report here](https://github.com/Mawelous/yamop/issues)
475 |
476 |
477 | ## License
478 |
479 | Yamop is free software distributed under the terms of the [MIT license](http://opensource.org/licenses/MIT)
480 |
--------------------------------------------------------------------------------
/tests/Mawelous/Yamop/Tests/MapperTest.php:
--------------------------------------------------------------------------------
1 | _getSimpleData();
13 | $mapper = new Mapper( '\Model\Simple' );
14 | $result = $mapper->fetchObject( $data );
15 |
16 | $this->assertInstanceOf( '\Model\Simple', $result );
17 | $this->assertSame( $data, get_object_vars( $result ) );
18 |
19 | }
20 |
21 | public function testFindReturnsMapper()
22 | {
23 | $data = $this->_saveSimpleData();
24 |
25 | $mapper = new Mapper( '\Model\Simple' );
26 | $return = $mapper->find( $data );
27 |
28 | $this->assertInstanceOf( '\Mawelous\Yamop\Mapper', $return );
29 | $this->assertTrue( \Model\Simple::$isCollectionNameCalled );
30 |
31 | }
32 |
33 | public function testFindSetsCursor()
34 | {
35 |
36 | $mapper = new Mapper( '\Model\Simple' );
37 |
38 | $emptyCursor = $mapper->getCursor();
39 |
40 | $this->assertSame( null, $emptyCursor );
41 |
42 | $mapper->find( array( 'not_existing' => 'nothing' ) );
43 | $cursor = $mapper->getCursor();
44 |
45 | $this->assertTrue( \Model\Simple::$isCollectionNameCalled );
46 | $this->assertInstanceOf( '\MongoCursor', $cursor );
47 | $this->assertEquals( 0, $cursor->count() );
48 |
49 | $data = $this->_saveSimpleData();
50 |
51 | $result = $mapper->find( $data );
52 | $cursor = $mapper->getCursor();
53 |
54 | $this->assertInstanceOf( '\MongoCursor', $cursor );
55 | $this->assertEquals( 1, $cursor->count() );
56 |
57 |
58 | }
59 |
60 | public function testFindOneAsObjectNoSettings()
61 | {
62 | $data = $this->_saveSimpleData();
63 |
64 | $mapper = new Mapper( '\Model\Simple' );
65 |
66 | $object = $mapper->findOne( $data );
67 |
68 | $this->assertInstanceOf( '\Model\Simple', $object );
69 | $this->assertAttributeNotEmpty( '_id', $object );
70 | $this->assertAttributeInstanceOf( '\MongoId', '_id', $object );
71 | $this->assertAttributeNotEmpty( 'test', $object );
72 | }
73 |
74 | public function testFindOneAsObjectAfterSettings()
75 | {
76 | $data = $this->_saveSimpleData();
77 |
78 | $mapperOne = new Mapper( '\Model\Simple', Mapper::FETCH_OBJECT );
79 | $objectOne = $mapperOne->findOne( $data );
80 |
81 | $this->assertInstanceOf( '\Model\Simple', $objectOne );
82 |
83 | $mapperTwo = new Mapper( '\Model\Simple' );
84 | $mapperTwo->setFetchType( Mapper::FETCH_OBJECT );
85 | $objectTwo = $mapperTwo->findOne( $data );
86 |
87 | $this->assertInstanceOf( '\Model\Simple', $objectTwo );
88 |
89 | }
90 |
91 | public function testFindOneAsArray()
92 | {
93 | $data = $this->_saveSimpleData();
94 |
95 | $mapperOne = new Mapper( '\Model\Simple', Mapper::FETCH_ARRAY );
96 | $result = $mapperOne->findOne( $data );
97 |
98 | $this->assertInternalType( 'array', $result );
99 | $this->assertArrayHasKey( '_id', $result );
100 | $this->assertInstanceOf( '\MongoId', $result['_id'] );
101 | $this->assertArrayHasKey( 'test', $result );
102 |
103 | $mapperTwo = new Mapper( '\Model\Simple' );
104 | $mapperTwo->setFetchType( Mapper::FETCH_ARRAY );
105 | $result = $mapperTwo->findOne( $data );
106 |
107 | $this->assertInternalType( 'array', $result );
108 | $this->assertArrayHasKey( '_id', $result );
109 | $this->assertInstanceOf( '\MongoId', $result['_id'] );
110 | $this->assertArrayHasKey( 'test', $result );
111 |
112 | }
113 |
114 | public function testFindOneAsJson()
115 | {
116 | $data = $this->_saveSimpleData();
117 |
118 | $mapperOne = new Mapper( '\Model\Simple', Mapper::FETCH_JSON );
119 | $result = $mapperOne->findOne( $data );
120 |
121 | $this->assertInternalType( 'string', $result );
122 | $decoded = json_decode( $result );
123 | $this->assertInstanceOf( 'stdClass', $decoded );
124 | $this->assertAttributeNotEmpty( '_id', $decoded );
125 | $this->assertAttributeInstanceOf( 'stdClass', '_id', $decoded );
126 | $this->assertAttributeNotEmpty( 'test', $decoded );
127 |
128 | $mapperTwo = new Mapper( '\Model\Simple' );
129 | $mapperTwo->setFetchType( Mapper::FETCH_JSON );
130 | $result = $mapperTwo->findOne( $data );
131 |
132 | $this->assertInternalType( 'string', $result );
133 | $decoded = json_decode( $result );
134 | $this->assertInstanceOf( 'stdClass', $decoded );
135 | $this->assertAttributeNotEmpty( '_id', $decoded );
136 | $this->assertAttributeInstanceOf( 'stdClass', '_id', $decoded );
137 | $this->assertAttributeNotEmpty( 'test', $decoded );
138 |
139 | }
140 |
141 | public function testFindById()
142 | {
143 | $mongoId = new \MongoId();
144 | $stringId = (string)$mongoId;
145 | $data = $this->_getSimpleData();
146 | $data[ '_id' ] = $mongoId;
147 | self::$_dbConnection->simple->insert( $data );
148 |
149 | $byString = ( new Mapper( '\Model\Simple') )->findById( $stringId );
150 |
151 | $this->assertInstanceOf( '\Model\Simple', $byString );
152 |
153 | $byMongoId = ( new Mapper( '\Model\Simple') )->findById( $mongoId );
154 |
155 | $this->assertInstanceOf( '\Model\Simple', $byMongoId );
156 | }
157 |
158 | public function testFindAndGetWithoutFetchSet()
159 | {
160 | $data = $this->_saveListData();
161 | $mapper = new Mapper( '\Model\Simple' );
162 | $result = $mapper->find()->get();
163 |
164 | $this->assertInternalType( 'array', $result );
165 |
166 | $keys = array_keys( $result );
167 |
168 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
169 | $this->assertInstanceOf( '\Model\Simple', current( $result ) );
170 | $this->assertCount( count( $data ), $result );
171 | }
172 |
173 | public function testFindAndGetWithFetchObjectSet()
174 | {
175 | $data = $this->_saveListData();
176 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_OBJECT );
177 | $result = $mapper->find()->get();
178 |
179 | $this->assertInternalType( 'array', $result );
180 |
181 | $keys = array_keys( $result );
182 |
183 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
184 | $this->assertInstanceOf( '\Model\Simple', current( $result ) );
185 | $this->assertCount( count( $data ), $result );
186 |
187 | $mapper = new Mapper( '\Model\Simple' );
188 | $mapper->setFetchType( Mapper::FETCH_OBJECT );
189 | $result = $mapper->find()->get();
190 |
191 | $this->assertInternalType( 'array', $result );
192 |
193 | $keys = array_keys( $result );
194 |
195 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
196 | $this->assertInstanceOf( '\Model\Simple', current( $result ) );
197 | $this->assertCount( count( $data ), $result );
198 | }
199 |
200 | public function testFindAndGetWithFetchObjectNoKeysSet()
201 | {
202 | $data = $this->_saveListData();
203 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_OBJECT_NO_KEYS );
204 | $result = $mapper->find()->get();
205 |
206 | $this->assertInternalType( 'array', $result );
207 |
208 | $keys = array_keys( $result );
209 |
210 | $this->assertEquals( 0, $keys[0] );
211 | $this->assertInstanceOf( '\Model\Simple', current( $result ) );
212 | $this->assertCount( count( $data ), $result );
213 |
214 | $mapper = new Mapper( '\Model\Simple' );
215 | $mapper->setFetchType( Mapper::FETCH_OBJECT_NO_KEYS );
216 | $result = $mapper->find()->get();
217 |
218 | $this->assertInternalType( 'array', $result );
219 |
220 | $keys = array_keys( $result );
221 |
222 | $this->assertEquals( 0, $keys[0] );
223 | $this->assertInstanceOf( '\Model\Simple', current( $result ) );
224 | $this->assertCount( count( $data ), $result );
225 | }
226 |
227 | public function testFindAndGetWithFetchArraySet()
228 | {
229 | $data = $this->_saveListData();
230 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_ARRAY );
231 | $result = $mapper->find()->get();
232 |
233 | $this->assertInternalType( 'array', $result );
234 |
235 | $keys = array_keys( $result );
236 |
237 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
238 | $current = current( $result );
239 | $this->assertInternalType( 'array', $current );
240 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
241 | $this->assertCount( count( $data ), $result );
242 |
243 | $mapper = new Mapper( '\Model\Simple' );
244 | $mapper->setFetchType( Mapper::FETCH_ARRAY );
245 | $result = $mapper->find()->get();
246 |
247 | $this->assertInternalType( 'array', $result );
248 |
249 | $keys = array_keys( $result );
250 |
251 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
252 | $current = current( $result );
253 | $this->assertInternalType( 'array', $current );
254 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
255 | $this->assertCount( count( $data ), $result );
256 |
257 | }
258 |
259 | public function testFindandGetArray()
260 | {
261 | $data = $this->_saveListData();
262 | $mapper = new Mapper( '\Model\Simple' );
263 | $result = $mapper->find()->getArray();
264 |
265 | $this->assertInternalType( 'array', $result );
266 |
267 | $keys = array_keys( $result );
268 |
269 | $this->assertEquals( (string)$data[0]['_id'], $keys[0] );
270 | $current = current( $result );
271 | $this->assertInternalType( 'array', $current );
272 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
273 | $this->assertCount( count( $data ), $result );
274 | }
275 |
276 | public function testFindAndGetWithFetchNumericArraySet()
277 | {
278 | $data = $this->_saveListData();
279 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_NUMERIC_ARRAY );
280 | $result = $mapper->find()->get();
281 |
282 | $this->assertInternalType( 'array', $result );
283 |
284 | $keys = array_keys( $result );
285 |
286 | $this->assertSame( 0, $keys[0] );
287 | $current = current( $result );
288 | $this->assertInternalType( 'array', $current );
289 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
290 | $this->assertCount( count( $data ), $result );
291 |
292 | $mapper = new Mapper( '\Model\Simple' );
293 | $mapper->setFetchType( Mapper::FETCH_NUMERIC_ARRAY );
294 | $result = $mapper->find()->get();
295 |
296 | $this->assertInternalType( 'array', $result );
297 |
298 | $keys = array_keys( $result );
299 |
300 | $this->assertSame( 0, $keys[0] );
301 | $current = current( $result );
302 | $this->assertInternalType( 'array', $current );
303 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
304 | $this->assertCount( count( $data ), $result );
305 |
306 | }
307 |
308 | public function testFindandGetNumericArray()
309 | {
310 | $data = $this->_saveListData();
311 | $mapper = new Mapper( '\Model\Simple' );
312 | $result = $mapper->find()->getArray(false);
313 |
314 | $this->assertInternalType( 'array', $result );
315 |
316 | $keys = array_keys( $result );
317 |
318 | $this->assertSame( 0, $keys[0] );
319 | $current = current( $result );
320 | $this->assertInternalType( 'array', $current );
321 | $this->assertEquals( (string)$data[0]['_id'], (string) $current['_id'] );
322 | $this->assertCount( count( $data ), $result );
323 | }
324 |
325 | public function testFindAndGetWithFetchJsonSet()
326 | {
327 | $data = $this->_saveListData();
328 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_JSON );
329 | $result = $mapper->find()->get();
330 |
331 | $this->assertInternalType( 'string', $result );
332 |
333 | $result = json_decode( $result );
334 | $current = current( $result );
335 |
336 | $this->assertInternalType( 'array', $result );
337 | $this->assertInstanceOf( 'stdClass', $current );
338 | $this->assertAttributeNotEmpty( '_id', $current );
339 | $this->assertCount( count( $data ), $result );
340 |
341 | $mapper = new Mapper( '\Model\Simple' );
342 | $mapper->setFetchType( Mapper::FETCH_JSON );
343 | $result = $mapper->find()->get();
344 |
345 | $this->assertInternalType( 'string', $result );
346 |
347 | $result = json_decode( $result );
348 | $current = current( $result );
349 |
350 | $this->assertInternalType( 'array', $result );
351 | $this->assertInstanceOf( 'stdClass', $current );
352 | $this->assertAttributeNotEmpty( '_id', $current );
353 | $this->assertCount( count( $data ), $result );
354 |
355 | }
356 |
357 | public function testFindAndGetJson()
358 | {
359 | $data = $this->_saveListData();
360 | $mapper = new Mapper( '\Model\Simple' );
361 | $result = $mapper->find()->getJson();
362 |
363 | $this->assertInternalType( 'string', $result );
364 |
365 | $result = json_decode( $result );
366 | $current = current( $result );
367 |
368 | $this->assertInternalType( 'array', $result );
369 | $this->assertInstanceOf( 'stdClass', $current );
370 | $this->assertAttributeNotEmpty( '_id', $current );
371 | $this->assertCount( count( $data ), $result );
372 | }
373 |
374 | public function testFindAndGetCursor()
375 | {
376 | $data = $this->_saveListData();
377 | $mapper = new Mapper( '\Model\Simple', Mapper::FETCH_JSON );
378 | $result = $mapper->find()->getCursor();
379 |
380 | $this->assertInstanceOf( '\MongoCursor', $result );
381 | $this->assertEquals( count( $data ), $result->count() );
382 | }
383 |
384 | /**
385 | * @expectedException Exception
386 | */
387 | public function testSortWithoutFind()
388 | {
389 | ( new Mapper( 'Model\Simple' ) )->sort( array( 'letter' => 1 ) );
390 | }
391 |
392 | /**
393 | * @expectedException Exception
394 | */
395 | public function testLimitWithoutFind()
396 | {
397 | ( new Mapper( 'Model\Simple' ) )->limit();
398 | }
399 |
400 | /**
401 | * @expectedException Exception
402 | */
403 | public function testSkipWithoutFind()
404 | {
405 | ( new Mapper( 'Model\Simple' ) )->skip();
406 | }
407 |
408 | public function testSort()
409 | {
410 | $data = $this->_saveListData();
411 | $mapper = new Mapper( '\Model\Simple' );
412 | $sortedAsc = $mapper->find()->sort( array( 'letter' => 1 ) )->get();
413 |
414 | $first = array_shift( $sortedAsc );
415 | $last = array_pop( $sortedAsc );
416 |
417 | $this->assertAttributeEquals( 'a', 'letter', $first );
418 | $this->assertAttributeEquals( 'c', 'letter', $last );
419 |
420 | }
421 |
422 | public function testLimit()
423 | {
424 | $limit = 2;
425 | $data = $this->_saveListData();
426 | $mapper = new Mapper( '\Model\Simple' );
427 | $limited = $mapper->find()->limit( $limit )->get();
428 |
429 | $this->assertCount( $limit, $limited );
430 |
431 | }
432 |
433 | public function testSkip()
434 | {
435 | $skip = 2;
436 | $data = $this->_saveListData();
437 | $mapper = new Mapper( '\Model\Simple' );
438 | $limited = $mapper->find()->skip( $skip )->get();
439 |
440 | $this->assertCount( count( $data ) - $skip, $limited );
441 |
442 | }
443 |
444 | public function testJoinWithVariable()
445 | {
446 | $this->_saveArticleWithAuthor();
447 |
448 | $articles = \Model\Article::getMapper()->find()->join( 'author', '\Model\Author', 'authorObject' )->get();
449 | $article = array_shift( $articles );
450 |
451 | $this->assertAttributeNotEmpty( 'authorObject', $article );
452 | $this->assertAttributeInstanceOf( '\Model\Author', 'authorObject', $article );
453 | $authorData = $this->_getAuthorData();
454 | $this->assertEquals( $authorData['name'], $article->authorObject->name );
455 | $this->assertEquals( $authorData['email'], $article->authorObject->email );
456 | }
457 |
458 | public function testJoinWithoutVariable()
459 | {
460 | $this->_saveArticleWithAuthor();
461 |
462 | $articles = \Model\Article::getMapper()->find()->join( 'author', '\Model\Author' )->get();
463 | $article = array_shift( $articles );
464 |
465 | $this->assertAttributeNotEmpty( 'author', $article );
466 | $this->assertAttributeInstanceOf( '\Model\Author', 'author', $article );
467 | $authorData = $this->_getAuthorData();
468 | $this->assertEquals( $authorData['name'], $article->author->name );
469 | $this->assertEquals( $authorData['email'], $article->author->email );
470 | }
471 |
472 | public function testJoinWithFields()
473 | {
474 | $this->_saveArticleWithAuthor();
475 |
476 | $articles = \Model\Article::getMapper()->find()->join( 'author', '\Model\Author', 'author', array( 'name' ) )->get();
477 | $article = array_shift( $articles );
478 |
479 | $this->assertAttributeNotEmpty( 'author', $article );
480 | $this->assertAttributeInstanceOf( '\Model\Author', 'author', $article );
481 | $authorData = $this->_getAuthorData();
482 | $this->assertEquals( $authorData['name'], $article->author->name );
483 | $this->assertObjectNotHasAttribute('email',$article->author);
484 | }
485 |
486 | public function testJoinToNull()
487 | {
488 | $this->_saveArticleWithAuthor();
489 | self::$_dbConnection->articles->insert( array ( 'title' => 'test', 'author' => null ) );
490 |
491 | $articles = \Model\Article::getMapper()->find()->join( 'author', '\Model\Author' )->get();
492 | $article = array_shift( $articles );
493 |
494 | $this->assertAttributeNotEmpty( 'author', $article );
495 | $this->assertAttributeInstanceOf( '\Model\Author', 'author', $article );
496 |
497 | $article = array_shift( $articles );
498 | $this->assertAttributeSame( null, 'author', $article );
499 |
500 | }
501 |
502 | public function testCallNativeBatchInsert()
503 | {
504 | $data = $this->_getListData();
505 | $mapper = new Mapper( '\Model\Simple' );
506 | $mapper->batchInsert( $data, array( 'continueOnError' => true, 'timeout' => 10000 ) );
507 |
508 | $dbData = self::$_dbConnection->simple->find();
509 |
510 | $this->assertInstanceOf( 'MongoCursor', $dbData );
511 | $this->assertCount( count( $data ), $dbData );
512 | $dbDataArray = iterator_to_array($dbData,false);
513 | $this->assertEquals( (string)$data[0]['_id'], (string) $dbDataArray[0]['_id'] );
514 | $this->assertEquals( $data[1]['letter'], $dbDataArray[1]['letter'] );
515 |
516 | }
517 |
518 | public function testCallNativeInsertWithoutW()
519 | {
520 | $mapper = new Mapper( '\Model\Simple' );
521 | $result = $mapper->insert( $this->_getSimpleData(), array( 'w' => 0) );
522 | $this->assertTrue($result);
523 |
524 | $dbData = self::$_dbConnection->simple->find();
525 |
526 | $this->assertInstanceOf( 'MongoCursor', $dbData );
527 | $this->assertCount( 1, $dbData );
528 |
529 | }
530 |
531 | protected function _getSimpleData()
532 | {
533 | return array( 'test' => 'test' );
534 | }
535 |
536 | protected function _getListData()
537 | {
538 | return array(
539 | array( 'letter' => 'b', '_id' => new \MongoId( '51d57f68b7846c9816000003' ) ),
540 | array( 'letter' => 'c'),
541 | array( 'letter' => 'a')
542 | );
543 | }
544 |
545 | protected function _getAuthorData()
546 | {
547 | return array( 'name' => 'test', 'email' => 'author@example.com' );
548 | }
549 |
550 | protected function _saveSimpleData()
551 | {
552 | self::$_dbConnection->simple->insert( $this->_getSimpleData() );
553 | return $this->_getSimpleData();
554 | }
555 |
556 | protected function _saveListData()
557 | {
558 | self::$_dbConnection->simple->batchInsert( $this->_getListData() );
559 | return $this->_getListData();
560 | }
561 |
562 | protected function _saveArticleWithAuthor()
563 | {
564 | $author = $this->_getAuthorData();
565 | self::$_dbConnection->authors->save( $author );
566 | self::$_dbConnection->articles->insert( array ( 'title' => 'test', 'author' => $author['_id'] ) );
567 | }
568 |
569 | }
--------------------------------------------------------------------------------