YiiMongoDbSuite Manual 1.3.6

Copyright © 2011 CleverIT
Available under the terms of license: GNU Free Documentation License 2.1
Generated: 28.01.2011

Table of Contents

Table of Contents
1. Introduction
2. Changelog
Next »

1. Introduction

AuthorDariusz Górecki

This extension is an almost complete, ActiveRecord like support for MongoDB in Yii

It originally started as a fork of MongoRecord extension written by tyohan, to fix some major bugs, and add full featured suite for MongoDB developers.

Limitations:

Requirements:

Known bugs

Resources

Contribution needed!

Big thanks goes to:

Table of Contents
2. Changelog
1. Introduction
« Previous
3. Setup
Next »

2. Changelog

AuthorDariusz Górecki

This is the 1.3.6 release

New features in 1.3.6

New features in 1.3.5

New features in 1.3.4

New features in 1.3.3

New features in 1.3.2

New features in 1.3.1

New features in 1.3

New features in 1.2.3

New features in 1.2.2

New features in 1.2.1

New features in 1.2

New features in 1.1

New features in 1.0:

Features:

Table of Contents
3. Setup
2. Changelog
« Previous
4. Basic Usage
Next »

3. Setup

AuthorDariusz Górecki

In your main configuration file, witch is by default: protected/config/main.php config file.

Add the following to the file:

'import' => array(
    // ...
    'ext.YiiMongoDbSuite.*',
),
 
'components' => array(
    // ...
    'mongodb' => array(
        'class'             => 'EMongoDB',
        'connectionString'  => 'mongodb://localhost',
        'dbName'            => 'myDatabaseName',
        'fsyncFlag'         => false,
        'safeFlag'          => false,
        'useCursor'         => false,
    ),
    // ...
),

That's all you have to do for setup. You can use it very much like the active record. Short example:

$client = new Client;
$client->first_name='something';
$client->save();
$clients = Client::model->findAll();
Table of Contents
4. Basic Usage
3. Setup
« Previous
4.1. Simple Model (Document in collection)
Next »

4. Basic Usage

AuthorDariusz Górecki

This section shows some basic, quick-start usage examples, follow to the next chapters.

EMongoDocument and EMongoEmbeddedDocument extends standard CModel class, you can do with them all things that can be done with standard CModel objects!

4. Basic Usage
4.1. Simple Model (Document in collection)
4. Basic Usage
« Previous
4.2. Simple Embeded Document Model
Next »

4.1. Simple Model (Document in collection)

AuthorDariusz Górecki

For basic use, just declare simple model, under yours application models directory, we will show simple User model that will represent documents stored in users MongoDB collection.

class User extends EMongoDocument // Notice: We extend EMongoDocument class instead of CActiveRecord
{
    public $personal_no;
    public $login;
    public $first_name;
    public $last_name;
    public $email;
 
    /**
     * This method have to be defined in every Model
     * @return string MongoDB collection name, witch will be used to store documents of this model
     */
    public function getCollectionName()
    {
        return 'users';
    }
 
    // We can define rules for fields, just like in normal CModel/CActiveRecord classes
    public function rules()
    {
        return array(
            array('login, email, personal_no', 'required'),
            array('personal_no', 'numeric', 'integerOnly' => true),
            array('email', 'email'),
        );
    }
 
    // the same with attribute names
    public function attributeNames()
    {
        return array(
            'email' => 'E-Mail Address',
        );
    }
 
    /**
     * This method have to be defined in every model, like with normal CActiveRecord
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($classname);
    }
}

Now we can star using our model, just as normal Yii ActiveRecord!

$user = new User();
$user->personal_no = 1234;
$user->login = 'somelogin';
$user->email = 'email@example.com';
$user->save(); // This will store document with user data into MongoDB collection
4. Basic Usage
4.2. Simple Embeded Document Model
4.1. Simple Model (Document in collection)
« Previous
4.3. Embedded Documents
Next »

4.2. Simple Embeded Document Model

AuthorDariusz Górecki

To fully understand this example please refer to Embedded Documents Models Section

To use embedded documents you need to do two steps:

/**
 * Note that we extending EMongoEmbeddedDocument, not regular EMongoDocument class
 * this is for performance reasons, embedded documents do not need to have, all
 * connection, and collection management stuff, this is explained in [Embedded Documents Models Section][basic.embeddedDocuments]
 *
 * NOTE: You may define regular EMongoDocument as an embedded in another one, if you really want to, it will still work
 */
class UserAddress extends EMongoEmbeddedDocument
{
    public $apartment;
    public $house;
    public $street;
    public $city;
    public $zip;
 
    // We may define rules for embedded document too
    public function rules()
    {
        return array(
            array('apartment, house, street, city, zip', 'required'),
            // ...
        );
    }
 
    // And attribute names too
    public function attributeNames() { /* ... */ }
 
    // NOTE: for embedded documents we do not define static model method!
    //       we do not define getCollectionName method either.
}

Add embeddedDocument() method to previous User model example:

class User extends EMongoDocument
{
    // ...
 
    /**
     * This method should return simple array that will define field names for embedded
     * documents, and class to use for them
     */ 
    public function embeddedDocuments()
    {
        return array(
            // property field name => class name to use for this embedded document
            'address' => 'UserAddress',
        );
    }
 
    // ...
}

Now, the fun part starts!

We can now do things like:

$user = new User();
$user->address->city = 'New York';
$user->address->street = 'Some street name';
 
// This will save user to users collection, with UserAddress embedded document set,
// and this handle with validation of embedded documents too!
$user->save();
 
// After that:
$user = User::model()->find();
 
// Models will be automatically populated with embedded documents that they contain,
// so we can do:
echo $user->address->city;
4. Basic Usage
4.3. Embedded Documents
4.2. Simple Embeded Document Model
« Previous
4.4. Arrays
Next »

4.3. Embedded Documents

AuthorDariusz Górecki

Basic informations about embedded documents

Must know:

Defining embedded documents within document

This applies to EMongoDocument and EMongoEmbeddedDocument

Just define the embeddedDocuments() method in yours model class, it should return array of simple key => value pairs.

example:

// ...
// within model class
public function embeddedDocuments()
{
    return array(
        'address' => 'UserAddress',
        'some_other_field_name' => 'AnyEMongoEmbeddedDocumentChildClass',
    );
}
 
// this will give you access to propeties of model:
$model->address->embeddedExampleField;
$model->some_other_field_name->embeddedExampleField;

How to force save of embedded document into collection

4. Basic Usage
4.4. Arrays
4.3. Embedded Documents
« Previous
4.4.1. Simple Arrays
Next »

4.4. Arrays

AuthorDariusz Górecki

You can in extreme simple way store any arrays in MongoDB, next sections will describe how to get it done.

4.4. Arrays
4.4.1. Simple Arrays
4.4. Arrays
« Previous
4.4.2. Array of Embedded Documents
Next »

4.4.1. Simple Arrays

AuthorDariusz Górecki

Just define a property in yours model, and store an array in it! YES This is that simple

Example:

class User extends EMongoDocument
{
    // ...
 
    public $myArray;
 
    // ...
}
 
$myArray = array('our', 'simple', 'example', 'array');
 
$user = new User();
 
$user->myArray = $myArray;
$user->save();
// that's it!
// in any time after when you get yours model form DB
$user = User::model()->find();
 
echo $user->myArray[1];
// will return 'simple' 
4.4. Arrays
4.4.2. Array of Embedded Documents
4.4.1. Simple Arrays
« Previous
5. Querying
Next »

4.4.2. Array of Embedded Documents

AuthorDariusz Górecki

There is no way (that I know) where I can easily provide mechanism for this, you have to write Your own

This is how I accomplish it for now.

Within suite package you should have and extra folder that contains (not only) EEmbeddedArraysBehavior class, we can use it to store an arrays of embedded documents.

Example:

class User extends EMongoDocument
{
    // ...
 
    // Add property for storing array
    public $addresses;
 
    // Add EEmbeddedArraysBehavior
    public function behaviors()
    {
        return array(
            'embeddedArrays' => array(
                'class'=>'ext.YiiMongoDbSuite.extra.EEmbeddedArraysBehavior',
                'arrayPropertyName'=>'addresses',       // name of property, that will be used as an array
                'arrayDocClassName'=>'ClientAddress'    // class name of embedded documents in array
            ),
        );
    }
 
    // ...
}

Now you can use property arrays as an array of embedded documents:

$user = new User();
$user->addresses[0] = new ClientAddress();
$user->addresses[0]->city = 'New York';
 
$user->save(); // Behavior will automatically handle of validation, saving etc.
 
// OR:
 
$user = User::model()->find();
foreach($user->addresses as $userAddress)
{
    echo $userAddress->city;
}
Table of Contents
5. Querying
4.4.2. Array of Embedded Documents
« Previous
5.1. Simple queries
Next »

5. Querying

AuthorDariusz Górecki

This is one of the things that makes this extension great. It's very easy to query for the objects you want.

This section covers all possibilities of querying the DB for results.

Basically this covers usage of EMongoCriteria object.

5. Querying
5.1. Simple queries
5. Querying
« Previous
5.2. The EMongoCriteria Object
Next »

5.1. Simple queries

AuthorDariusz Górecki

You can use methods that you have used to with standard Yii CActiveRecord's

$model = ModelClass::model()->find()

$model = ModelClass::model()->findByAttributes(array('attributeName'=>'attributeValue', 'attribute2'=>'otherValue'))

$model = ModelClass::model()->findByPk(new MongoID(/* ... */))

To understand PK queries refer to [Primary Keys Section][advanced.primaryKeys]

This is almost the same behavior like standard Yii ActiveRecord models, refer to them for a basic idea

5. Querying
5.2. The EMongoCriteria Object
5.1. Simple queries
« Previous
6. The Advanced Stuff
Next »

5.2. The EMongoCriteria Object

AuthorDariusz Górecki

By default MongoDB requires to build advanced query arrays if you want advanced search for documents in DB.

The EMongoCriteria object simplifies process of building this query arrays.

First of all create the object instance: $criteria = new EMongoCriteria();

You may define query criteria in three simple ways:

  1. Simple 'equlas' condition, that is equivalent of SQL: WHERE fieldName = 'fieldValue':
    • $criteria->fieldName = 'fieldValue';
  2. By field name call ie. SQL: WHERE fieldName > 1234:
    • $criteria->fieldName('>', 1234);
  3. By addCond method ie. SQL: WHERE fieldName <= 1234:
    • $criteria->addCond('fieldName', '<=', 1234);

You can add multiple conditions, using any of the above techniques, they will be mixed together.

In addition to above the criteria object has additional special methods.

  1. limit($value) that takes as parameter the query results limit, same as SQL LIMIT directive
  2. offset($value) that takes as parameter the query results offset to get, same as SQL LIMIT directive second parameter
  3. sort($fieldName, $sortDirection) this will be covered shortly
  4. select($array) this will be covered shortly

If you want to add simple criteria, that will use field name that is a method name from the above list, you can use the addCond method ie. $criteria->addCond('limit', '==', 1234);

sort EMongoCriteria method

The sort method takes two parameters

You can call sort method as many times as you wish, the sorting criteria will be mixed together.

select EMongoCriteria method

The select method takes only one argument, array of field names.

By default MongoDB will return all fields from of document, use this method to tell mongo that you want only specified field list.

  • MongoDB regardless of anything, will always return the _id field within result sets
  • You can call sort method multiple times, all calls will be merged using array_merge function

List of supported operators

List consist of operators and after | available shortcut (if any)

Operators are case-insensitive

- 'greater'   | >
- 'greaterEq' | >=
- 'less'      | <
- 'lessEq'    | <=
- 'notEq'     | !=, <>
- 'in'        |
- 'notIn'     |
- 'all'       |
- 'size'      |
- 'exists'    |
- 'notExists' |
- 'type'      | // BSON type see mongodb docs for this
- 'mod'       | %
- 'equals'    | ==
- 'elemMatch' |

the $or operator in newer versions of mongodb does NOT work with this extension yet. We will add it to the list above when it is fixed.

Newer versions of mongodb will work, just not the $or operator.

For examples and use for how to use these operators effectively, use the MongoDB Operators Documentation here.

Examples:

// first you must create a new criteria object
$criteria = new EMongoCriteria;
 
// find the single user with the personal_number == 12345
$criteria->personal_number('==', 12345);
// OR like this:
$criteria->personal_number = 12345; 
 
$user = User::model->find($criteria);
 
// find all users in New York. This will search in the embedded document of UserAddress
$criteria->address->city('==', 'New York');
// Or
$criteria->address->city = 'New York';
$users = User::model()->findAll($criteria);
 
// Ok now try this. Only active users, only show at most 10 users, and sort by first name, descending, and offset by 20 (pagination):
// note the sort syntax. it must have an array value and use the => syntax.
$criteria->status('==', 1)->limit(10)->sort(array('firstName' => EMongoCriteria::SORT_DESC))->offset(20);
$users = User::model()->findAll($criteria);
 
// A more advanced case. All users with a personal_number evenly divisible by 10, sorted by first name ascending, limit 10 users, offset by 25 users (pagination), and remove any address fields from the returned result.
$criteria->personal_number('%', array(10, 0)) // modulo => personal_number % 10 == 0
    ->sort(array('firstName' => EMongoCriteria::SORT_ASC))
    ->limit(10)
    ->offset(25);
$users = User::model()->findAll($criteria);

Regexp / SQL LIKE replacement

You can use native PHP Mongo driver class MongoRegex, to query:

// Create criteria
$criteria = new EMongoCriteria;
// Find all records witch have first name starring on a, b and c, case insensitive search
$criteria->first_name = new MongoRegex('/[abc].*/i');
$clients = Client::model()->findAll($criteria);
// see phpdoc for MongoRegex class for more examples

Creating criteria object from an array:

// Example criteria
$array = array(
    'conditions'=>array(
        // field name => operator definition
        'FieldName1'=>array('greaterEq' => 10), // Or 'FieldName1'=>array('>=' => 10)
        'FieldName2'=>array('in' => array(1, 2, 3)),
        'FieldName3'=>array('exists'),
    ),
    'limit'=>10,
    'offset'=>25,
    'sort'=>array('fieldName1' => EMongoCriteria::SORT_ASC, 'fieldName4' => EMongoCriteria::SORT_DESC),
);
 
$criteria = new EMongoCriteria($array);
// or
$clients = ClientModel::model()->findAll($array);
Table of Contents
6. The Advanced Stuff
5.2. The EMongoCriteria Object
« Previous
6.1. Indexing
Next »

6. The Advanced Stuff

AuthorDariusz Górecki

YiiMongoDbSuite enables you to do even more fun things!

6. The Advanced Stuff
6.1. Indexing
6. The Advanced Stuff
« Previous
6.2. Primary Keys
Next »

6.1. Indexing

AuthorDariusz Górecki

Now you can define indexes for yours collections in easy way!

Suite will check for existing of indexes only once, at the first class use, (per script-calls) if it not find any of declared indexes it will create them

Only thing you need is to define the indexes() method in yours model class, see the example:

class Client extends EMongoDocument
{
    // ....
    public function indexes()
    {
        return array(
            // index name is not important, you may write whatever you want, just must be unique
            'index1_name'=>array(
                // key array holds list of fields for index
                // you may define multiple keys for index and multikey indexes
                // each key must have a sorting direction SORT_ASC or SORT_DESC
                'key'=>array(
                    'field_name'=>EMongoCriteria::SORT_ASC
                    'field_name.embeded_field'=>EMongoCriteria::SORT_DESC
                ),
 
                // unique, if indexed field must be unique, define a unique key
                'unique'=>true,
            ),
        );
    }
    // ....
}

If you whant to disable index existing checking on every page load, because of perfomance reasons (recomended for production) put $this->ensureIndexes = false; into yours init() method in model class.

class Client extends EMongoDocument
{
    // ....
    public function init()
    {
        $this->ensureIndexes = false;
    }
    // ....
}
6. The Advanced Stuff
6.2. Primary Keys
6.1. Indexing
« Previous
6.3. Named Scopes
Next »

6.2. Primary Keys

AuthorDariusz Górecki

By default YiiMongoDbSuite will always use the _id field as a collection primary key.

MongoDB itself guarantee that _id field will be unique across collection.

The _id field

The _id field in a short is, be default an instance of MongoID class.

If you will do echo $model->_id you will see something like: '4ced75e1eb4ae8ca44000000'

This is basically, the textual representation of auto-generated by MongoDB _id field.

But be aware that MongoID is not a string!

This will not find anything! $model->findByPk('4ced75e1eb4ae8ca44000000');

To find an model with specified ID use:

$model->findByPk(new MongoID('4ced75e1eb4ae8ca44000000'));

You can put any value into an _id field

MongoDB will auto populate _id field by MongoID objects only when it is set to NULL

Own defined Primary Key for collection

You can define a own field to be used as a Primary Key for yours collection, see example:

class Client extends EMongoDocument
{
    // ...
    // Define primaryKey method:
    public function primaryKey()
    {
        return 'personal_number'; // Model field name, by default the _id field is used
    }
 
    // Now you can:
    // Client::model()->findByPk(1234); // personal_number == 1234
    // ...
}
6. The Advanced Stuff
6.3. Named Scopes
6.2. Primary Keys
« Previous
6.4. Relations
Next »

6.3. Named Scopes

AuthorDariusz Górecki

Now you can use AR style named scopes just define scopes method in yours model:

class Client extends EMongoDocument
{
    // (...)
 
    public function scopes()
    {
        return array(
            'scopeName'=>array(/* Array for criteria object creation see Criteria from array topic */),
        );
    }
 
    public function defaultScope()
    {
        return array(
            'conditions'=>array(
                'active'=>array('==', true),
            ),
        );
    }
 
    // (...)
}
 
// now You can:
// this will find only clients that are active (default scope) and clients matching 'scopeName'
$all = Client::model()->scopeName()->findAll();

Parameterized Named Scopes

class Client extends EMongoDocument
{
    // (...)
 
    public function byStatus($status)
    {
        $criteria = $this->getDbCriteria();
        $criteria->status = $status;
        $this->setDbCriteria($criteria);
 
        return $this;
    }
 
    // (...)
}
 
// now You can:
// this will find only clients that have status set to parameter value:
$all = Client::model()->byStatus(1)->findAll();

You can chain multiple named scopes

example: Client::model()->active()->byStatus(1)->findAll();

6. The Advanced Stuff
6.4. Relations
6.3. Named Scopes
« Previous
6.5. Data Provider Object
Next »

6.4. Relations

AuthorDariusz Górecki

HAS_ONE relation

// just define method in yours model class (assume we have client collection, and address collection in client model
public function address()
{
    return Address::model()->findByAttributes(array('attribute_with_client_id'=>$this->primaryKey()));
}

BELONGS_TO relation

// define in address model
public function client()
{
    return Client::model()->findByPk($this->attribute_with_client_id);
}

HAS_MANY relation

 
 
// assume we have clients and orders collection
// define in client:
public function orders()
{
    return Client::model()->findAllByAttributes(array('client_id'=>$this->primaryKey()));
}

MANY_MANY relation

As simple as defining reverse HAS_MANY relation in orders model. You can view the Example models for details.

6. The Advanced Stuff
6.5. Data Provider Object
6.4. Relations
« Previous
6.6. Write Queries Flags
Next »

6.5. Data Provider Object

AuthorDariusz Górecki

// basic dataprovider returns whole collection (with efficient pagination support out-of-box)
$dp = new EMongoDocumentDataProvider('modelClassNameOrInstance');
 
// data provider with query
$dp = new EMongoDocumentDataProvider('modelClassNameOrInstance', array(
    'conditions'=>/* query array goes here */
));
 
// data provider, enable sorting (needs to be set explicit)
$dp = new EMongoDocumentDataProvider('modelClassNameOrInstance', array(
    /* standard config array */
    'conditions'=>array(/* query array goes here */)
    'sort'=>array(
        'attributes'=>array(
            // list of sortable attributes
        )
    ),
));
6. The Advanced Stuff
6.6. Write Queries Flags
6.5. Data Provider Object
« Previous
6.7. Document Partial Updates
Next »

6.6. Write Queries Flags

AuthorDariusz Górecki

Quote from PHP Manual:

"safe"

Can be a boolean or integer, defaults to FALSE. If FALSE, the program continues executing without waiting for a database response. If TRUE, the program will wait for the database response and throw a MongoCursorException if the write operation did not succeed.

If you are using replication and the master has changed, using "safe" will make the driver disconnect from the master, throw and exception, and attempt to find a new master on the next operation (your application must decide whether or not to retry the operation on the new master).

If you do not use "safe" with a replica set and the master changes, there will be no way for the driver to know about the change so it will continuously and silently fail to write.

If safe is an integer, will replicate the insert to that many machines before returning success (or throw an exception if the replication times out, see wtimeout). This overrides the w variable set on the collection.

"fsync"

Boolean, defaults to FALSE. Forces the insert to be synced to disk before returning success. If TRUE, a safe insert is implied and will override setting safe to FALSE.

We now can set value for write queries flags FSync and/or Safe on a different scopes:

State of write queries flags is connected

Setting FSync to TRUE, will set Safe flag to TRUE automatically

Now, we may want all DB operations to be synced to disk, and we set global FSync flag in MongoDB component config, but we feagure out that, for ie. massive temporary models like DB logging objects are not important, and for performance reasons, we whant to disable FSync for this models only.

We may do so: LogModelClass::model()->setFsyncFlag(false);

From now on, all LogModelClass objects will be written to DB without waiting to filesystem sync.

For user comfort, FSync and Safe flags may be set just for single model instance by, $singleModelInstance->setFsyncFlag(false), this will not affect any other model instance.

6. The Advanced Stuff
6.7. Document Partial Updates
6.6. Write Queries Flags
« Previous
6.8. Massive Partial Updates
Next »

6.7. Document Partial Updates

AuthorDariusz Górecki

Since the v1.3.5 You can do partial updates of documents.

Normally this extension, when using EmongoDocument::save() will always replace all contents of document in DB with the actual values of fields in Yours EMongoDocument instance (including all embedded documents etc.)

You can pass to EMongoDocument::update() method a list of attributes for update, this will use an extreme efficient Mongo $set operator to update only listed fields, see example:

$model = ModelClass::model()->find(); // Find some document to work with
 
// change some fields
$model->field1 = 1;
$model->field2 = 'value';
 
// Optional: run validation, of changed fields:
$model->validate(array('field1', 'field2'));
 
// Use partial update
$model->update(array('field1', 'field2'), true /* <- second parameter indicates to use partial update mechanism */);

And thats it, this will only update those 2 fields, rest in DB will not be touched.

6. The Advanced Stuff
6.8. Massive Partial Updates
6.7. Document Partial Updates
« Previous
6.9. GridFS
Next »

6.8. Massive Partial Updates

AuthorPhilippe Gaultier

Since the v1.3.6 You can perform partial updates of multiple documents.

// prepare modifiers
$modifier = new EMongoModifier();
// replace field1 value with 'new value'
$modifier->addModifier('field1', 'set', 'new value');
// increment field2 value by 1
$modifier->addModifier('field2', 'inc', 1);
 
// prepare search to find documents
$criteria = new EMongoCriteria();
$criteria->addCond('field3','==', 'filtered value');
 
// update all matched documents using the modifiers
$status = ModelClass::model()->updateAll($modifier, $criteria); 

And thats it, this will only update those 2 fields (force value of field1 and increment value of field2) for all the documents having field3 == 'filtered value', everything else in the db will remain untouched.


Available modifiers are :

You can find detailed explanation about usage of those modifiers on the original MongoDb documentation.


EMongoModifier can be defined during creation :

// prepare modifiers
$modifier = new EMongoModifier(
    array(
        'fieldName1'=>array('inc' => $incValue),
        'fieldName2'=>array('set' => $targetValue),
        'fieldName3'=>array('unset' => 1),
        'fieldName4'=>array('push' => $pushedValue),
        'fieldName5'=>array('pushAll' => array($pushedValue1, $pushedValue2)),
        'fieldName6'=>array('addToSet' => $addedValue),
        'fieldName7'=>array('pop' => 1),
        'fieldName8'=>array('pop' => -1),
        'fieldName9'=>array('pull' => $removedValue),
        'fieldName10'=>array('pullAll' => array($removedValue1, $removedValue2)),
        'fieldName11'=>array('rename' => $newFieldName),
    )
);

, during execution

$modifier = new EMongoModifier();
$modifier->addCond($fieldName1, 'inc', $incValue),
$modifier->addCond($fieldName2, 'set', $targetValue),
$modifier->addCond($fieldName3, 'unset', 1),
$modifier->addCond($fieldName4, 'push', $pushedValue),
$modifier->addCond($fieldName5, 'pushAll', array($pushedValue1, $pushedValue2)),
$modifier->addCond($fieldName6, 'addToSet', $addedValue),
$modifier->addCond($fieldName7, 'pop', 1),
$modifier->addCond($fieldName8, 'pop', -1),
$modifier->addCond($fieldName9, 'pull', $removedValue),
$modifier->addCond($fieldName10, 'pullAll', array($removedValue1, $removedValue2)),
$modifier->addCond($fieldName11, 'rename', $newFieldName),

or using the two methods

6. The Advanced Stuff
6.9. GridFS
6.8. Massive Partial Updates
« Previous
6.10. Soft Documents Models
Next »

6.9. GridFS

AuthorDariusz Górecki

You can use MongoDB GridFS feature to save big files within MongoDB collections.

The first thing you need to do, it to define model for yours GridFS collection, see example:

class Image extends EMongoGridFS
{
    /**
     * This field is optional, but:
     * from PHP MongoDB driver manual:
     *
     * 'You should be able to use any files created by MongoGridFS with any other drivers, and vice versa.
     * However, some drivers expect that all metadata associated with a file be in a "metadata" field.
     * If you're going to be using other languages, it's a good idea to wrap info you might want them
     * to see in a "metadata" field.'
     *
     * @var array $metadata array of additional info/metadata about a file
     */
    public $metadata = array();
 
    // this method should return the collection name for storing files
    public function getCollectionName()
    {
        return 'images';
    }
 
    // normal rules method, if you use metadata field, set it as a 'safe' attribute
    public function rules()
    {
        return array(
            array('filename, metadata','safe'),
            array('filename','required'),
        );
    }
 
    /**
     * Just like normal ActiveRecord/EMongoDocument classes
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

Every EMongoGridFS has a special field "filename", that is mandatory!

EMongoGridFS has a feature that holds files in temporary folder, between operations, it can be set on global level in EMongoDB class, by setting property gridFStemporaryFolder to some dirname, or on model/object level by using gridFStemporaryFolder() setter

This part of documentation is not yet complete, for now, follow usage examples:

Yii::import('ext.YiiMongoDbSuite.examples.MongoImage');
 
//create a new image
$image = new MongoImage();
$image->filename = '/var/www/myImage.JPG';
$image->metadata = array('value1'=>1, 'value2'=>2);
 
$res = $image->save();
 
if($res !== true)
    echo 'error saving file';
 
//find image
 
$image = MongoImage::model()->find();
 
if(!($image instanceof MongoImage))
    echo 'error finding object';
 
//findall images
 
$images = MongoImage::model()->findAll();
 
if(!is_array($images))
    echo 'error on findall';
 
//delete images
 
$image = MongoImage::model()->find();
 
if($image instanceof MongoImage)
{
    $result = $image->delete();
    if($result !== true)
        echo 'delete notok';
}
else
    echo 'no image found to delete';
 
//deleteAll images
 
$result = MongoImage::model()->deleteAll();
 
if(is_array($result)===true)
{
    echo ' isarray';
    if(isset($result['err']) === true )
        echo ' error deleting images:'.$result['err'];
    else
        echo ' elements deleted:'.$result['n'];
}
 
//deletebyPk image
$image = MongoImage::model()->find();
if($image instanceof MongoImage)
{
    $result = MongoImage::model()->deleteByPk($image->_id);
    if($result !== true)
        echo 'error';
}
else
    echo 'no image found to delete by pk';
 
//insert image and update
 
$image = new MongoImage();
 
$image->filename = '/var/www/myImage.JPG';
$image->metadata = array('value1'=>1, 'value2'=>2);
$res = $image->save();
 
$image->filename =  '/var/www/myImageUpdated.JPG';
$image->metadata = array('value1'=>3, 'value2'=>4);
$res = $image->save();
 
if($res !== true)
    echo 'error updating';
 
//MongoGridFSFile functions
 
$image = MongoImage::model()->find();
 
//getBytes function
 
$bytes = $image->getBytes();
 
//getFilename
 
$filename = $image->getFilename();
 
//getSize
 
$size = $image->getSize();
 
//write
 
$image->write();
6. The Advanced Stuff
6.10. Soft Documents Models
6.9. GridFS
« Previous
7. Gii Support
Next »

6.10. Soft Documents Models

AuthorDariusz Górecki

Since the 1.3.4 version you can have models, that do not have fixed attribute list.

The only thing to get started is to define model that extends from EMongoSoftDocument

Example:

class MixedModel extends EMongoSoftDocument
{
    // You still can define a field(s), that always will be defined
    // like in normal EMongoDocument, but this is optional
    public $field;
 
    // As always define the getCollectionName() and model() methods !
 
    public function getCollectionName()
    {
        return 'mixed_collection';
    }
 
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

And thats it! Now you have model class that will handle of any field lists!

You still can use all features that are present in EMongoDocument because 'EMongoSoftDocument` extends from it.

Usage:

You need to init every soft attribute that you want to add to model, by using $model->initSoftAttribute($attributeName).

$model = new MixedModel();
 
$model->initSoftAttribute('field1');
$model->initSoftAttributes(array('field2', 'field3'));
 
$model->field = 'regularField';
$model->field1 = 'value';   //  }
$model->field2 = 'value2';  //  } soft attributes, only in this model instance
$model->field3 = 'value3';  //  }
 
$model->save();
 
// Finder will init and populate all soft attributes with values found in particular document automatically
$newModel = MixedModel::model()->find();
 
echo $newModel->field3; // will print 'value3'
Table of Contents
7. Gii Support
6.10. Soft Documents Models
« Previous
8. Special Topics
Next »

7. Gii Support

AuthorDariusz Górecki

Model generation

Since version 1.3.4 you can generate mongo document models based on existing SQL tables, just enable gii generators provided within package (see below) and use MongoModel generator.

CRUD Generation

Now you can generate CRUD for yours MongoDB Documents! just you have to add generator path to yours Gii config

By default generated models will use mongo _id field as a primary key (using MongoId class) this generator will use different field as a primary key, if you will override primaryKey() method to return something different than '_id'. Note that generator cannot handle with multifield primary key,

// in yours main.php config file:
'modules'=>array(
    // ...
 
    'gii'=>array(
        'class'=>'system.gii.GiiModule',
        'password'=>'yours_password_to_gii',
        // add additional generator path
        'generatorPaths'=>array(
            'ext.YiiMongoDbSuite.gii'
        ),
    ),
 
    // ...
),
// Now login to Gii and start using Mongocrud generator !

Forms generation

Table of Contents
8. Special Topics
7. Gii Support
« Previous
8.1. Multimodel Collections
Next »

8. Special Topics

AuthorDariusz Górecki

This section covers some special topics, that need to bye mentioned

Behaviors

Performance

Use Cursor Flag

Now we can set that all records returned by findAll* methods will return EMongoCursor instead of raw array, EMongoCursor implements Iterator interface and can be used with ie. foreach loop.

EMongoCursor will instantiate objects in lazy-loading way, only on first use.

By default useCursor flag is set to FALSE to keep backwards compatibility, note: - Cursor does not implement ArrayAccess interface, because offset access to cursor is very ineffective, and pointless - You can set useCursor flag in different scopes - globally in EMongoDB class, by setting $useCursor variable - Per model: ModelClass::model()->setUseCursor(true); - And per model instance: $modelInstance->setUseCursor(true);

Massive hand operations

// Example mass insert (you will want to disable fsyncField for this:
for($i=0; $i<1000; $i++)
{
    $c = new Client;
    $c->personal_number = $i;
    $c->validate(); // You can omit this if you want
    $c->getCollection()->insert($c->toArray());
}
8. Special Topics
8.1. Multimodel Collections
8. Special Topics
« Previous

8.1. Multimodel Collections

AuthorDariusz Górecki

You can have different models in single collection, example:

class Client extends EMongoDocument
{
    public $first_name;
    public $second_name;
 
    // define property for finding type
    public $type;
    // and some const for better remember
    const NORMAL_CLIENT = 0;
    const BUSINESS_CLIENT = 1;
 
    public function getCollectionName()
    {
        return 'clients';
    }
 
    /**
     * We can override the instantiate method to return correct models
     */
    protected function instantiate($attributes)
    {
        if($attributes['type'] == self::NORMAL_CLIENT)
            $model = new NormalClient(null); // We have to set scenario to null, it will be set, by populateRecord, later
        else if($attributes['type'] == self::BUSINESS_CLIENT)
            $model = new BusinessClient(null);
 
        $model->initEmbeddedDocuments(); // We have to do it manually here!
        $model->setAttributes($attributes, false);
        return $model;
    }
 
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}
 
class NormalClient extends Client
{
    public $additionalField;
 
    public function defaultScope()
    {
        return array(
            'conditions'=>array('type'=>array('==' => self::NORMAL_CLIENT)),
        );
    }
 
    public function beforeSave()
    {
        if(parent::beforeSave())
        {
            $this->type = self::NORMAL_CLIENT;
            return true;
        }
        else return false
    }
 
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}
 
class BusinessClient extends Client
{
    public $taxNo;
 
    public function defaultScope()
    {
        return array(
            'conditions'=>array('type'=>array('==' => self::BUSINESS_CLIENT)),
        );
    }
 
    public function beforeSave()
    {
        if(parent::beforeSave())
        {
            $this->type = self::BUSINESS_CLIENT;
            return true;
        }
        else return false
    }
 
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

Now we can:

// now you can:
$bclients = BuissnessClient::model()->findAll();
$clients = NormalClient::model()->findAll();
 
$allClients = Client::model()->findAll();
 
// but they're kept in single collection ! and can be indexed with single field etc.