Introduction

There has been a lot of talk about document databases and how applicable they are for Web 2.0 application development. There are a bunch of projects in the “NoSQL” realm that are making a lot of noise, CouchDB, Cassandra, and MongoDB are the ones that are most mentioned. MongoDB is a scalable document database, which is also open source.

Part of our business is to develop internal applications and  support customers, so we dedicate a good amount of time to research, test and learn new technologies. We have been following this whole movement and decided to give MongoDB a try.

What has really taken our attention regarding MongoDB is its simplicity, yet how powerful it is. It lowers the barrier to develop DB-driven applications even more.

In MongoDB there are no tables and rows. You have collections and documents instead. And collections can contain any type of document, it is not limited by the columns or fields like a table. In some scenarios, this is a really big plus. Yet, some people, mostly those born and raised with RDBMS will be scared to death due to the lack of schema and rigid structures. As always, if you plan ahead and do your homework and design your entities properly, you will be fine.

Since collections can have different types of documents, let’s say, an array, you can update the document with more fields in the array. In the RDBM world to do this, you have to change your schema, making application upgrades one of the biggest pains.

Using MongoDB and PHP

To get started with MongoDB and PHP, you need to download and install the server. From the website you can download packages for your preferred OS or you can download the source and compile it. Installation is really simple. Once you have the files in your computer, to start the server all you have to do is run the daemon, ie.:

# /usr/local/mongodb/bin/mongod

MongoDB will start and initialize its DB storage.

Now you can connect with the mongo command line client or other clients like MongoHub, PHPMOAdmin among others.

To connect to MongoDB from PHP, you will need to install the mongo pecl extension. On Mac OS X or Linux, run:

# pecl install mongo

Then add the following line to php.ini and restart your web server:

extension=mongo.so

The PHP.net site has a very good tutorial on how to get started with MongoDB and PHP. Basically, to store something in MongoDB you follow these simple steps:

// connect to the default DB server, localhost
$connection = new Mongo(); 

// select a DB
$db = $connection->dbname;

// get a collection, if it does not exist, it will get created
$collection = $db->users;

// insert a document
$collection->insert( $doc );

That’s it! No table creation, nice! For more information on how to retrieve data, go to the PHP.net tutorial.
MongoDB stores data in a binary JSON format. If you are familiar with JavaScript, you will know JSON. When you store a PHP object, when it is time to get the data from the DB, you will get an array instead. For many uses, this is OK, but some times, for more complex solutions, objects are necessary. Fortunately there is a solution already!

Enter ODM (Object Document Mapper)

ODM is a new project created by Jon Wage, a core developer of the popular Doctrine ORM. The Doctrine\ODM namespace will be the home to PHP 5.3 object mappers for Document based storage systems like MongoDB. Currently only the Doctrine\ODM\MongoDB code has been implemented and it will map PHP objects with MongoDB documents. So when you store PHP objects in MongoDB and you need to retrieve them, you will get back PHP objects. Not only that, but it also helps with relations between objects. The project home page includes a basic usage manual and a full working example.

What’s really nice about this solution is how unintrusive it is. Your classes are pure PHP classes without having to extend other classes. The only requirement is that you map your properties using one of the support metadata drivers. In this example we use the AnnotationDriver:

namespace Entities;

/** @Entity */
class User
{
    /** @Field */
    private $id;

    /** @Field */
    private $username;

    /** @Field */
    private $password;

    /** @Field(embedded="true", targetEntity="Entities\Phonenumber", type="many", cascadeDelete="true") */
    private $phonenumbers = array();

    /** @Field(embedded="true", targetEntity="Entities\Address", type="many") */
    private $addresses = array();

    /** @Field(reference="true", targetEntity="Entities\Profile", type="one") */
    private $profile;

    /** @Field(reference="true", targetEntity="Entities\Account", cascadeDelete="true") */
    private $account;

    public function getId()
    {
        return $this->id;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function setPassword($password)
    {
        $this->password = md5($password);
    }

    public function getAddresses()
    {
        return $this->addresses;
    }

    public function addAddress(Address $address)
    {
        $this->addresses[] = $address;
    }

    public function setProfile(Profile $profile)
    {
        $this->profile = $profile;
    }

    public function getProfile()
    {
        return $this->profile;
    }

    public function setAccount(Account $account)
    {
        $this->account = $account;
    }

    public function getAccount()
    {
        return $this->account;
    }

    public function getPhonenumbers()
    {
        return $this->phonenumbers;
    }

    public function addPhonenumber(Phonenumber $phonenumber)
    {
        $this->phonenumbers[] = $phonenumber;
    }
}

Take a look at the DocBlocks and how we map the properties to the document.

Once you defined your entities, the usage is super simple:

require '/path/to/doctrine/lib/Doctrine/Common/ClassLoader.php';

use Doctrine\Common\ClassLoader,
      Doctrine\Common\Annotations\AnnotationReader,
      Doctrine\ODM\MongoDB\Mongo,
      Doctrine\ODM\MongoDB\Configuration,
      Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver,
      Doctrine\ODM\MongoDB\Mapping\Driver\YamlDriver,
      Doctrine\ODM\MongoDB\EntityManager;

$classLoader = new ClassLoader('Doctrine\ODM', __DIR__ . '/../lib');
$classLoader->register();

$classLoader = new ClassLoader('Doctrine', '/path/to/doctrine/lib');
$classLoader->register();

$classLoader = new ClassLoader('Entities', __DIR__);
$classLoader->register();

$config = new Configuration();

$reader = new AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ODM\MongoDB\Mapping\Driver\\');
$config->setMetadataDriverImpl(new AnnotationDriver($reader, __DIR__ . '/Entities'));

$em = EntityManager::create(new Mongo(), $config);

$user = new User();
$user->username = 'jwage';
$user->password = 'changeme';

$user->profile = new Profile();
$user->profile->firstName = 'Jonathan';
$user->profile->lastName = 'Wage';

$user->account = new Account();
$user->account->name = 'Test Account';

$em->persist($user);
$em->flush();

For more information, visit the project home page. Please note that this project is in very early stages of development, so things may change.

MongoDB, ODM and symfony

We will demonstrate how to use MongoDB with ODM and symfony. Symfony 2 is still in alpha stage, so even though it would be a perfect fit with ODM, we will stick with symfony 1.4 for now. For this part of the article, you will need to know a little bit of symfony.

1. If you don’t have a symfony project, go ahead and create it with ./symfony generate:project myproject

2. Download Doctrine2 (you will need the new class autoloader) and ODM into myproject/lib/vendor

3. Add the class auotoloader to the config/ProjectConfiguration.class.php file:

require_once __DIR__.'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();

require __DIR__.'/../lib/vendor/doctrine2/lib/Doctrine/Common/ClassLoader.php';

use Doctrine\Common\ClassLoader, Doctrine\ODM\MongoDB\EntityManager;

class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    $classLoader = new ClassLoader('Doctrine\ODM', __DIR__ . '/../lib/vendor/odm/lib');
    $classLoader->register();

    $classLoader = new ClassLoader('Doctrine\ODM', __DIR__ . '/../lib/vendor/doctrine2/lib');
    $classLoader->register();

    $classLoader = new ClassLoader('Entities', __DIR__.'/../lib');
    $classLoader->register();

  }
}

4. Create your classes in lib/Entities, ie. lib/Entities/Server.php:

namespace Entities;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;

class Server
{
  private $id;
  private $hostname;
  private $ipAddress;
  private $status = 0;

    public function getId()
    {
        return $this->id;
    }

    public function getHostname()
    {
        return $this->hostname;
    }
    public function setHostname($hostname)
    {
        $this->hostname = $hostname;
    }
   public function getIpAddress()
    {
        return $this->ipAddress;
    }
    public function setIpAddress($ip)
    {
        $this->ipAddress = $ip;
    }

  public function  __toString()
  {
    return $this->hostname;
  }

}

5. Now you can perform your operations, just to keep it simple, we will add the code to the actions.inc.php:

use Doctrine\ODM\MongoDB\EntityManager, Doctrine\ODM\MongoDB\Mongo, Entities\Server;
class defaultActions extends sfActions
{
 public function executeInex(sfWebRequest $request)
  {
    $em = EntityManager::create(new Mongo());

    $s = new Server();
    $s->setIpAddress('192.168.0.1');
    $s->setHostname('abc.example.com');
    $em->persist($s);
    $em->flush();
    $query = $em->createQuery('Entities\Server');
    $servers = $query->execute();
    $this->servers = $servers;
  }

And your indexSuccess.php has nothing special about ODM:

Servers:
<ul>
<?php foreach( $servers as $s): ?>
 <li><?php echo esc_entities($s);?></li>
<?php endforeach; ?>
</ul>

Hopefully with this preview, you can see the power of MongoDB when joined by ODM and symfony.

Special thanks to Jon Wage for leading the development of this project and his contribution to this article.