Skip to content

samdark/YiiMongoDbSuite

 
 

Repository files navigation

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!:

Nasty BUG found! When using multiple Mongo Documents within single script documents, have used only one (first used) collection, because of static field in EMongoDocument, this applies to all version below 1.0.2, please Update asap!

This is the 1.0.2 release

New features in 1.0:

  • Named scopes just like in AR
  • Very easy to use criteria object, you don't have to create complex MongoDB query arrays!
  • Better exeption handling
  • A lot more PHPDocs in code

Features:

  • Support of using Class::model()->find / findAll / count / countByAttributes and other Yii ActiveRecord syntax
  • Support of schema-less documents with Yii standard rules and validation features
  • Endless Embedded/Nested document support (Embedded documents are their own Model class with their own rules and other methods.
  • (almost, limited only by MongoDB 4MB limit of single document) endless document embedding/nesting
  • Ready to go out-of-box EFFICIENT DataProvider, witch use native php db driver sort, limit and offset features for returning results!
  • Records and embedded documents inherit from CModel, so you can use every class witch can handle of CModel (ie: Gii form generator)
  • relation support idea/concept/example

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

  • Has only been tested on Yii 1.1.4 and 1.1.5 . If it works for you on an older version please let me know.
  • MongoDB latest stable is recommended. Untested with older versions.

##Basic usage

Just create a model class as You would with normal ActiveRecord. Example model simple as standard ActiveRecord:

##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:

[php]


    'import' => array(
      ...
      'ext.YiiMongoDbSuite.*',
    ),

    'components' => array(
      ...
      'mongodb' => array(
        'connectionString' => 'mongodb://localhost',
        'dbName' => 'myDatabaseName',
        'class'     => 'EMongoDB',
        'fsyncFlag' => true,
      ),
    ),


  • 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 is set to true, this makes mongodb make sure all writes to the database are safely stored to disk. Setting this to false will improve speed but writes ar enot confirmed. So unless you are doing massive or large imports or save() then it is better to be set to true. See the performance section below.
  • For more info see the MongoDB connection page on php.net.

To get started, you can look in the files of the project and there are two example files. a User model for the User document as well as a UserAddress model to represent an embedded document.

  • examples/User.php
  • examples/UserAddress.php

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

[php]


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


##Embedded documents

IMPORTANT: Embedded documents must extend from EMongoEmbeddedDocument instead of EMongoDocument, otherwise whole thing will fail, to work Note: some people reported that embedding regular EMongoDocument works as expected, but i have not tested this, more test/reports needed

Embedded documents are almost identical as EMongoDocument you only can't save them to DB they're saved by the EMongoDocument model witch has them as an embedded (with one exception, you can save them if you will do it explicitly) Notice: Embedded documents don't 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 (a full example is in the examples directory):

[php]


    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:

[php]


    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!

[php]


    $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:
[php]


    // 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:

[php]


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


or

[php]


    $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.

[php]


    // 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.

[php]


    $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.

[php]


    $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:

[php]


    // 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:

[php]


    // 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:

[php]


    // 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'),
        	'FieldName4'=>array('where', 'this.FieldName4 + this.FieldName3 > 10'),
        ),
        'limit'=>10,
        'offset'=>25,
        'sort'=>array('fieldName1'=>EMongoCriteria::SORT_ASC, 'fieldName4'=>EMongoCriteria::SORT_DESC),
    );
	$criteria = new EMongoCriteria($array);
	// or
	$clients = ClientModel::model()->findAll($array);

##Names Scopes

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

[php]


    class Client extends EMongoDocument
    {
    	// (...)
    	
    	public function scopes()
    	{
    	    return array(
    	    	'scopeName'=>array(/* Array for criteria object creation see above */),
    	    );
    	}
    	
    	// (...)
    }
    
    // now You can:
    
    $all = Client::model()->scopeName()->findAll();


##Relations

Note:

  • In NoSQL World Relations are not so obvious as in RDBMS
  • Because NoSQL databases are designed for performance, there is no need of defining something more complex than correct use of find() method and indexing for fetching related records
  • This is just an idea/concept/example you can do things in yours preferred way! this is schema-less wold, think different!
  • MongoDB Documentation has a clean examples and how-to's for handling relations, please read them for better understanding of relations in NoSQL world

###HAS_ONE relation

[php]


    // 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

[php]


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


###HAS_MANY relation

[php]


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


###MANY_MANY

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

##Gii support

Model generation

  • you do not have to generate yours models, yours classes are yours models and schemas!
  • Gii CAN'T generate models for you! there is no schema witch gii can exam for generation

CRUD Generation

  • Gii is not able to generate CRUD, because by default gii expects that CRUD have to be generated for CActiveRecord child classes (EMongoDocument is not child class of CActiveRecord)

Forms generation

  • This is a good news, Gii can generate the worst part of developer job, forms for mongo records ;]
  • When generating a form from mongo record, comment out embedded docs array, or you'll see error about array/object creation
  • For embedded docs just generate forms in separed way for each one

##DataProvider

[php]


    // 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(/* query array goes here */));

    // data provider, enable sorting (needs to be set explicit)
    $dp = new EMongoDocumentDataProvider('modelClassNameOrInstance', array(/* query array goes here */), array(/* standard config array */
        'sort'=>array(
            'attributes'=>array(
                // list of sortable attributes
            )
        ),
    ));


##Special topics

Behaviors

  • You can use existing CActiveRecordBehaviors as long as they do not mess up with CActiveRecord explicit stuff (ie. behaviors that add relation handling will fail to work with EMongoDocuments)
  • Behaviors may extend from EMongoDocumentBehavior class, standard AR behavior class can't use extra events witch are available here (beforeEmbeddedDocsInit, afterEmbeddedDocsInit, beforeToArray and afterToArray)

Performance

  • By default all save/update/delete operations performed by internal command sets the FSYNC flag to TRUE, this means, all operations will have to wait for a disk sync!
  • FSYNC in most cases is a good way and do not have massive impact on performance
  • You may want to disable FSYNC flag when doing massive imports/updates/models, because it will be horrible slow!
  • Fsync can be disabled by setting up fsyncField in EMongoDB class

Massive hand operations

[php]


    // 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());
    }


##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

##Changelog

  • Added EMongoDocumentBehavior (Support for new events present in EMongoDocument)
  • Added FSync control field fsyncFlag in EMongoDBConnection class (default set to true)
  • Changed the way of setup, db property name change to dbName
  • Added EEmbeddedArraysBehavior extra

##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

About

Yii Mongo DB Active Record extension

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published