Yii 1.1: yiimongodbsuite

Full-featured ActiveRecord like support for MongoDB in Yii
108 followers

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.

IMPORTANT Info: I've developed this extension as a hobby project. Now I had stopped using Yii in my hobby and work projects. I'm no longer maintaining this code. My codebase is available for easy fork/download on a github. Feel free to continue my work, YMDS already has active community, you can find support mostly on google groups (described bellow).

Flattr this

PLEASE refer to the new FULL-Documentation page

There is also an google groups for topics related to YMDS, everyone are welcome, to post threads, questions, and support requests there.

Work-around for using the OR operator with this extension provided in comments

This is the 1.4 preview release 1

New in 1.4 PR1

The YMDS feature list has grown up recently, some of new ones are:

  • MongoDB session handler, for in db session storage
  • Ability to set use cursors flag with criteria object
  • Support for using OR operator with criteria object
  • Many bugfixes

This is a Preview Release of YMDS 1.4 it is not ready for stable usage I publish it because I need help with testing and code stabilization, anyone are welcome to download, test and submit bug fixes.

Added in this release:

  • Few annoying bugs fixed
  • Documentation corrects
  • Added EMongoPartialDocument class, that supports full-featured partial loading of documents from DB
  • Added fixtures manager, that can replace the Yii default one, and work with Mongo model
  • Ability to do massive partial updates of multiple document

The Key Feature List:

Features covered from standard Yii ActiveRecord implementation

  • Support of using Class::model()->find / findAll / count / countByAttributes and other Yii ActiveRecord syntax
  • Named scopes, along with default scope and parameterized scopes, just like in AR
  • Ready to go out-of-box EFFICIENT DataProvider, witch use native php db driver sort, limit and offset features for returning results!
  • Model classes and embedded documents inherit from CModel, so you can use every class witch can handle of CModel (ie: Gii form generator)
  • Relations support idea/concept/example
  • Support for generating CRUD for EMongoDocument models, with Gii!
  • Support for generating mongo document models from existing SQL tables!
  • Very easy to use criteria object, you don't have to create complex MongoDB query arrays!

MongoDB related feature list

  • Support of schema-less documents with Yii standard rules and validation features
  • Embedded/Nested document support (Embedded documents have their own Model classes with their own rules and other methods)
  • (almost, limited only by MongoDB 4MB limit of single document) endless document embedding/nesting
  • Lazy loading/creating of embedded documents
  • Ability to set FSync and/or Safe flag of DB write operations on different scopes, globally, on model level, and on single model object
  • Ability to use efficient MongoDB Cursors instead of raw arrays of results, returned by the findAll* methods
  • MongoDB GridFS feature support, thanks to work of: Jose Martinez and Philippe Gaultier
  • Support for using any other than _id field as a Primary Key, for a collection
  • Support to have different models in single collection!
  • automated efficient index definition for collections, per model
  • Support "Soft" documents, documents that do not have fixed list of attributes
  • Ability to do Extreme Efficent document partial updates, that make use of MongoDB $set operator/feature
  • Support for PHP Mongo driver versions below 1.0.5
  • Support for partial loading of documents from DB

Limitations:

  • The main limitations are only those present in MongoDB itself, like the 4mb data transfer limit. But That's not a big deal either.
  • In it's current incarnation, This extension does NOT work with the "$or" criteria operator. When we get it working we will remove this line and add an example.

IMPORTANT: The version on GitHub is more up to date as fixes are pushed to the project. This may or may not get updated on a regular basis

Requirements

  • Yii 1.1.5 is required
  • MongoDB latest stable is recommended. Untested with older versions.

Setup

In your protected/config/main.php config file. Comment out (or delete) the current 'db' array for your database in the components section, and add the following to the file:

'import' => array(
      ...
      'ext.YiiMongoDbSuite.*',
    ),
 
    'components' => array(
      ...
      'mongodb' => array(
        'class'            => 'EMongoDB',
        'connectionString' => 'mongodb://localhost',
        'dbName'           => 'myDatabaseName',
        'fsyncFlag'        => true,
        'safeFlag'         => true,
        'useCursor'        => false
      ),
    ),
  • ConnectionString: 'localhost' should be changed to the ip or hostname of your host being connected to. For example if connecting to a server it might be 'connectionString' => 'mongodb://username@xxx.xx.xx.xx' where xx.xx.xx.xx is the ip (or hostname) of your webserver or host.
  • dbName: is the name you want the collections to be stored in. The database name.
  • fsyncFlag if is set to true, this makes mongodb make sure all writes to the database are safely stored to disk. (as default, true)
  • safeFlag if is set to true, mongodb will wait to retrieve status of all write operations, and check if everything went OK. (as default, true)
  • useCursors if is set to true, extension will return EMongoCursor instead of raw pre-populated arrays, form findAll* methods, (default to false, for backwards compatibility)

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

$client = new Client();
    $client->first_name='something';
    $client->save();
    $clients = Client::model()->findAll();

Basic usage

Just define following model:

class User extends EMongoDocument
    {
      public $login;
      public $name;
      public $pass;
 
      // This has to be defined in every model, this is same as with standard Yii ActiveRecord
      public static function model($className=__CLASS__)
      {
        return parent::model($className);
      }
 
      // This method is required!
      public function getCollectionName()
      {
        return 'users';
      }
 
      public function rules()
      {
        return array(
          array('login, pass', 'required'),
          array('login, pass', 'length', 'max' => 20),
          array('name', 'length', 'max' => 255),
        );
      }
 
      public function attributeLabels()
      {
        return array(
          'login'  => 'User Login',
          'name'   => 'Full name',
          'pass'   => 'Password',
        );
      }
    }

And thats it! Now start using this User model class like standard Yii AR model

Embedded documents

NOTE: For performance reasons embedded documents should extend from EMongoEmbeddedDocument instead of EMongoDocument

EMongoEmbeddedDocument is almost identical as EMongoDocument, in fact EMongoDocument extends from EMongoEmbeddedDocument and adds to it DB connection related stuff.

NOTE: Embedded documents should not have a static model() method!

So if you have a User.php model, and an UserAddress.php model which is the embedded document. Lest assume we have following embedded document:

class UserAddress extends EMongoEmbeddedDocument
    {
      public $city;
      public $street;
      public $house;
      public $apartment;
      public $zip;
 
      public function rules()
      {
        return array(
          array('city, street, house', 'length', 'max'=>255),
          array('house, apartment, zip', 'length', 'max'=>10),
        );
      }
 
      public function attributeLabels()
      {
        return array(
          'zip'=>'Postal Code',
        );
      }
    }

Now we can add this method to our User model from previous section:

class User extends EMongoDocument {
      ...
 
      public function embeddedDocuments()
      {
        return array(
          // property name => embedded document class name
          'address'=>'UserAddress'
        );
      }
 
      ...
    }

And using it is as easy as Pie!

$client = new Client;
    $client->address->city='New York';
    $client->save();

it will automatically call validation for model and all embedded documents! You even can nest embedded documents in embedded documents, just define embeddedDocuments() method with array of another embedded documents IMPORTANT: This mechanism uses recurrency, and will not handle with circular nesting, you have to use this feature with care :P

Arrays

You easily can store arrays in DB!

Simple arrays

  • just define a property for an array, and store an array in it.

Arrays of embedded documents

  • 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:
// add a property for your array of embedded documents
    public $addresses;
 
      // add EmbeddedArraysBehavior
    public function behaviors()
    {
      return array(
        array(
          'class'=>'ext.YiiMongoDbSuite.extra.EEmbeddedArraysBehavior',
          'arrayPropertyName'=>'addresses', // name of property
          'arrayDocClassName'=>'ClientAddress' // class name of documents in array
        ),
      );
    }

So for the user, if you want them to be able to save multiple addresses, you can do this:

$c = new Client;
    $c->addresses[0] = new ClientAddress;
    $c->addresses[0]->city='NY';
    $c->save(); // behavior will handle validation of array too

or

$c = Client::model()->find();
    foreach($c->addresses as $addr)
    {
        echo $addr->city;
    }

Querying

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

// simple find first. just like normal AR.
    $object = ModelClass::model()->find()

Now suppose you want to only retrieve users, that have a status of 1 (active). There is an object just for that, making queries easy.

$c = new EMongoCriteria;
    $c->status('==', 1);
    $users = ModelClass::model->findAll($c);

and now $users will be an array of all users with the status key in their document set to 1. This is a good way to list only active users. What's that? You only want to show the 10 most recent activated users? Thats easy too.

$c = new EMongoCriteria;
    $c->active('==', 1)->limit(10);
 
    $users = ModelClass::model->findAll($c);

It's that easy. In place of the 'equals' key, you can use any of the following operators


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


*NOTICE: 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 mongo db will work, just not the $or operator. For examples and use for how to use these operators effectively, use the MongoDB Operators Documentation here.

Here are a few more examples for using criteria:

// 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);
 
    // You can even use the where operator with javascript like so:
    $criteria->fieldName('where', ' expression in javascript ie: this.field > this.field2');
    // but remember that this kind of query is a bit slower than normal finds.

Regexp / SQL LIKE replacemt

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

for reference on how to use query array see: http://www.php.net/manual/en/mongocollection.find.php

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);

Known bugs

  • Remember, this is not complete yet. So at this stage, it can have some ;]
  • If you find any please let me know
  • As said before, it does not work with the OR operators

Resources

Contribution needed!

  • I'm not English native speaker, need someone who can correct/rewrite/write my documentation and/or PHPDoc's in code
  • Any help would be great :)
  • Contact me: darek.krk on a gmail dot com or via PM

Big thanks goes to:

  • tyohan: for first inspirations and idea of extension
  • luckysmack: for big help with testing and documentation
  • Jose Martinez and Philippe Gaultier, for implementing and sharing GridFS support
  • Nagy Attila Gábor, for big help with new functionality and testing

Total 20 comments

#14898 report it
michail at 2013/09/19 08:24am
"AND" operator and "NOT LIke"

I'm want to write a query "

SELECT * FROM page WHERE keyphrases NOT LIKE '%fghjkl%' AND keyphrases LIKE '%abcde%'

" $criteria = new EMongoCriteria; $criteria->keyphrases('!=', new MongoRegex("/fghjkl/")); $criteria->addCond('keyphrases','==', new MongoRegex("/abcde/"));

$clients = PageMongo::model()->findAll($criteria);

then I get an error 27017: invalid regular expression operator

#14395 report it
Sammaye at 2013/08/08 07:02am
Re: Breaking at validation rule

You don't use the default inbuilt inique validator, you need to use EMongoUniqueValidator

#14394 report it
riyazMuhammed at 2013/08/08 06:51am
Breaking at validation rule

Thanks for awesome extension !

BTW I have a problem, when I write a rule in my generated model as

array('email','unique'),

it gives the following error : and its behaviors do not have a method or closure named "tableName".

#13663 report it
fifik at 2013/06/14 04:24pm
Instantiation problem

Right... yes...

$model->initEmbeddedDocuments(); $model->setAttributes($attributes, false);

are in the parent method and I ommited them - I think they should be placed somewhere else.

sorry for bothering

F

#13662 report it
fifik at 2013/06/14 04:17pm
RE: RE: Instantiation problem

Sammaye, I honestly do not think it's that way. My fault with isNewRecord was, cause i created a new $class; and should do new $class() Unfortunately the records are still not being populated - tho I think they should be, I may check if the afterFind event is fired.

Does your MongoYii extension provide similiar feature, maybe I should look overthere?

#13659 report it
Sammaye at 2013/06/14 09:26am
RE: Instantiation problem

You will need to override populateRecord with the behaviour you need. This is the function that will be called, or rather should be, called from cursors and what not. I cannot totally remember how YiiMongoDBSuite calls it but I believe it does.

#13658 report it
fifik at 2013/06/14 09:10am
RE: RE: Instantiation problem

So what ?

I is populateRecord which invokes instantiate() and sets the scenario to update before it invokes init() and $model->afterFind();

or i still don't get a thing

F

#13657 report it
Sammaye at 2013/06/14 08:50am
RE: Instantiation problem

That is because instantiate will never set the update scenario (it is not supposed to), only populateRecord does.

#13655 report it
fifik at 2013/06/14 08:29am
Instantiation problem

Hello,

I have this issue with method instantiate($attributes)

I wanted to make a dynamic attribute model, by single table inheritance (you can read about it here: http://www.yiiframework.com/wiki/198/single-table-inheritance/).

protected function instantiate($attributes) {
    if($attributes['type'] === null) {
      $class = self::DEFAULT_POSITION_CLASS;
      return new $class;
    }
    $class = $attributes['type'];
    return new $class;
  }

Tho either I am doing something simply wrong and i'm blind on it, or after the instantiation, my object properties' values are tottaly empty (NULLs), and the funny part is, that the isNewRecord property is false.

Anyone has ever anything common with that ?

Cheers, F

#13526 report it
akimvital at 2013/06/04 04:53am
RE:yii mongodb listview

Use EMongoDocumentDataProvider like any CDataProvider and pass it to list/grid view anything else...

#13473 report it
flaviuspogacian at 2013/05/30 08:24am
yii mongodb listview

I can't use the listview when using this extension;

Can it be used? How?

#13431 report it
pmaselkowski at 2013/05/29 04:48am
Re: does this extension work ?

Yes, it work very well. You dont have php_mongo extension loaded in your php.ini.

#13428 report it
flaviuspogacian at 2013/05/29 04:22am
does this extension work ?

PHP warning

include(Mongo.php): failed to open stream: No such file or directory

#13253 report it
akimvital at 2013/05/18 03:44am
RE: Igoru-san Migrations?

Migration - it just a command, that can access to DB. Use Yii::app()->mongodb to access DB, and some collection like "migrations" to check for command execute status. It's same like migrations.

#13036 report it
akimvital at 2013/04/30 03:27am
Bug in EMongoDocument

EMongoDocument has a bug: it does not use _sort criteria when selecting one object using find method. As a result - wrong object selects.

I fix it for me in this way:

$cursor = $this->getCollection()->find($criteria->getConditions());
if($criteria->getSort() !== null)
    $cursor->sort($criteria->getSort());
if($criteria->getOffset() !== null)
    $cursor->skip($criteria->getOffset());
if($criteria->getSelect())
    $cursor->fields($criteria->getSelect(true));
$cursor->limit(1);
 
return $this->populateRecord($cursor->getNext());
#12692 report it
Igoru-san at 2013/04/05 11:28am
Migrations?

Hi!

I'm very interested in using this extension. However, I do use some other extensions that rely on migrations to setup some settings. Is there any plan on supporting some type of dummy migration to satisfy those needs? (when you are changing to a system that used to have migrations for example).

#12050 report it
sirin k at 2013/02/23 07:09pm
how to compare a string with larger paragraph which is stored in mongoDb?

how to compare a string with larger paragraph which is stored in mongoDb?

i want to select the rows which contain the given string in a field which stores a paragraph.Mine is a search features.Please help...

#10505 report it
canni at 2012/11/01 07:12am
@pmaselkowski

Annotations are great concept, but they require external dependecies and change of Yii users thinking (Yii does not use this anywhere, afaik)

#10441 report it
pmaselkowski at 2012/10/29 03:59am
Some of my ideas

But first, where you want that ideas to put? gitub issues, or here? Anyway, here's my ideas:

  1. Arbitrary type of embedded, embedded array - I already use it in my fork of ymds, just store _class field for each embed

  2. Gather all and each attribute metada in one place, as there are a lot of options like

  • Label
  • Validators
  • Embeded
  • Index
  • If it should be persistent
  • User defined

It would be great to use annotations for this. I developed yii annotation extension, but unfortunatelly it still requires more docs before I publish it. With annotations defining attributes is plain fun, like that:

/**
 * @Label('Master pages')
 * @Versioned
 * @CollectionName('blah')
 */
class Page extends EMongoEmbeddedDocument
{
    /**
     * @Label('Title')
     * @I18N(allowAny = true)
     * @RequiredValidator(skipOnError = true)
     * @var string
     */
    public $title = '';
 
    /**
     * @EmbeddedArray('PageTemplateRow')
     * @SafeValidator
     * @MyOwnAnnotation
     * @var PageTemplateRow[]
     */
    public $data = [];
  1. (<- it meant to be 3) i18n attributes - attribute value depended on language (or maybe more generic, on some predefined param?). The idea of i18n attribute is to use it in yii just like any normal attribute, so `$page->title` should return current language title, however in db it is stored as array
'title' => [
'en' => 'Blah',
'pl' => 'Foo'
];

here is my fork of ymds using annotations and above features: https://github.com/Maslosoft/YiiMongoDbSuite

#10417 report it
canni at 2012/10/26 09:46am
Suite v2 :)

Hey all,

loong 2 yeas have passed since I finished working with Yii and YMDS.

I'm glad to announce that I'm currently working on full re-write of YMDS.

YMDS2 will not be backward compatible with v1.x versions, as some core concepts where changed:

  • ORM/AR is not suitable for MongoDB extreme power of "schema-less", features that poorly tried to implement lack of schema, will be avoided, if you want to have maximum flexibility, use simple array() Luke
  • Every relation (eg. Embedded document) will have its own proxy class, I'll provide ref implementation for embedded document and array of embedded documents, you can write yours super-extra-featured relation classes.
  • Core Yii's AR "concept" will be handled, but ActiveRecord like interface is not guaranteed.
  • EMongoCritera is rewritten too, for support of complex operators like $or

If you want to help, submit some ideas, pull requests - some core concept code is already on GitHub https://github.com/canni/ymds2

I'll be glad if someone can help with docs(blocks) and proper english speech.

cheers, canni

Leave a comment

Please to leave your comment.

Create extension