├── .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 | } --------------------------------------------------------------------------------