MongoDB Guide

Contents

Installation and Setup

Refer to the download page for instructions on installing Phactory for PHP through Pear or to download the source code.

Once you’ve installed Phactory, you’re ready to start using it as a database factory in your unit tests. Whether you use PHPUnit, SimpleTest, or something else entirely, Phactory is easy to get started with.

To include the MongoDB version of the Phactory library in your test suite, just use the following statement in your Bootstrap or individual test file:

require_once 'Phactory/lib/PhactoryMongo.php';

Follow the rest of this guide to learn how to use Phactory for database test object creation in your unit tests, and make use of some of the more advanced features. You’ll find that Phactory is a lot easier and more flexible than traditional database fixtures.

Creating a Connection

Phactory uses a MongoDB object to perform all database operations.

To set Phactory’s database connection:

$mongo = new Mongo();
Phactory::setDb($mongo->test_db);

Defining Collection Blueprints

Before you can create a document with Phactory, you have to define a blueprint for the collection you want to use. The define() function takes a singular collection name and an optional array of default values. These defaults will be set on each document you create in this collection unless you override them at creation time.

Assuming you have a collection called ‘users’ in your database, the following will define a blueprint for that table:

Phactory::define('user', array('name' => 'test_user', 'age' => 20));

After this, every time you create a user document with Phactory it will have a name of ‘test_user’ and an age of 20 (unless you override these values).

Creating Objects

Once you have a database connection and you’ve defined a blueprint, you’re ready to create documents with Phactory.

Let’s assume your app uses a database with a ‘users’ collection, and you’ve already connected Phactory. The fields on documents in our ‘users’ collection are ‘id’, ‘name’, and ‘activated’. Here’s an example of how you might define and create some users:

Phactory::define('user', array('activated' => 0));

$john = Phactory::create('user', array('name' => 'John Doe'));
$joe  = Phactory::create('user', array('name' => 'Joe Smith', 'activated' => 1));
$anon = Phactory::create('user');

After this code runs, $john, $joe and $anon are all arrays, and a document for each has been inserted into the ‘users’ collection.

Notice that when we created $john and $joe we overrode at least one of the default values by passing an array as the second argument to create().

$john looks like this:

$john['id'] == 1;
$john['name'] == 'John Doe';
$john['activated'] == 0;

$joe looks like this:

$joe['id'] == 2;
$joe['name'] == 'Joe Smith';
$joe['activated'] == 1;

And $anon looks like this:

$anon['id'] == 3;
//$anon['name'] will not be set
$anon['activated'] == 0;

Getting a Document

Phactory provides a convenient get() method to get documents from your MongoDB. This method functions just like Mongo’s built-in findOne() method. So let’s say we want to get a user out of the db. As long as we know some information about that user, we can simply use the get() method. If the user’s name is ‘David Moore’ we can do the following:

$david = Phactory::get('user', array('name' => 'David Moore'));

The query parameter accepts any valid Mongo query, so you can use more advanced operators:

$joe = Phactory::get('user', array('name' => 'Joe Smith', 'age' => array('$gte' => 20), 'activated' => 1));

Sequences

Phactory provides the ability for your default values to contain an automatically-incrementing sequence. If you use this functionality, each successive document you create in a collection will have a different value even if you don't override the default.

To use a sequence, simply put $n in the value you pass to define(). The $n will be substituted with a number that starts at 0 and is incremented by 1 each time you create a document in that collection.

For example:

Phactory::define('user', array('name' => 'user$n', 'age' => '$n'));

$user0 = Phactory::create('user'); // name == 'user0', age == '0'
$user1 = Phactory::create('user'); // name == 'user1', age == '1'
$user2 = Phactory::create('user'); // name == 'user2', age == '2'

Be careful when using sequences with string interpolation (i.e. double quotes). The following will not create a sequence, and will instead insert the local variable $n (which is probably unset) as the default value:

/**
  * Don't use double quotes for defined values unless you really mean to.
  * If you want to use double quotes, make sure to escape the $n like: "\$n"
  */
Phactory::define('user', array('name' => "user$n")); //WRONG didn't escape $n in double quotes

$user0 = Phactory::create('user'); // name == 'user'
$user1 = Phactory::create('user'); // name == 'user'

Associations

Phactory provides support for defining the associations in the form of embedded documents. You can build a document of one type, and then embed it in another with or without saving it to the database. Two types of Mongo associations are provided: Phactory::embedsOne($type) and Phactory::embedsMany($type).

embedsOne

Phactory::define('author');

Phactory::define('book',
                 array('name' => 'Test Book'),
                 array('primary_author' => Phactory::embedsOne('author')));

$twain = Phactory::build('author', array('name' => 'Mark Twain'));

$book = Phactory::createWithAssociations('book', array('primary_author' => $twain));

$book['author']['name'] == $twain['name'];

Note that in the above example 'primary_author' is the name of the association, and not necessarily the name of any collection.

embedsMany

Phactory::define('tag');

Phactory::define('post',
                 array('name' => 'Test Blog Post'),
                 array('tags' => Phactory::embedsMany('tags'));

$tag = Phactory::build('tag', array('name' => 'PHP'));

$post = Phactory::createWithAssociations('post', array('tags' => array($tag)));

$post['tags'][0]['name'] == 'PHP';

Reset and Recall

Usually when you are unit testing, you'll want your database to be reset after each test. Similarly, you might want your Phactory blueprints to be cleared out after each test or each test suite.

Phactory::recall() will empty any collections you defined blueprints for.

Phactory::reset() clears out all blueprints definitions, and calls Phactory::recall() to empty those collections in the database.

A Complete PHPUnit Example Test

/**
  * This is the function we will test.
  * It should retrieve a user from the db by id,
  * and return that user's age.
  *
  * @param $users MongoCollection
  * @param int $user_id
  * @return mixed The age of the user, or false if no user
  */
function getUserAge($users, $user_id)
{
    $user = $users->findOne(array('_id' => $user_id));

    if(null === $user) {
        return false;
    }

    return $user['age'];
}

require_once 'Phactory/lib/PhactoryMongo.php';
class UserTest extends PHPUnit_Framework_TestCase
{
    public static function setUpBeforeClass()
    {
        // create a db connection and tell Phactory to use it
        $mongo = new Mongo();
        Phactory::setConnection($mongo->test_db);

        // reset any existing blueprints and empty any tables Phactory has used
        Phactory::reset();

        // define default values for each user we will create
        Phactory::define('user', array('name' => 'Test User $n', 'age' => 18));
    }

    public function tearDown()
    {
        Phactory::recall();
    }

    public function testGetUserAge()
    {
        // test that getUserAge() returns false for a nonexistent user
        $age = getUserAge(Phactory::getDb()->users, 0);
        $this->assertFalse($age);

        // create 20 users, with ages from 1-20
        $users = array();
        for($i = 1; $i <= 20; $i++) {
            // create a row in the db with age = $i and store in an array
            $users[] = Phactory::create('user', array('age' => $i));
        }

        // test that getUserAge() returns the correct age for each user
        foreach($users as $user) {
            $user_id = $user['_id'];

            $age = getUserAge(Phactory::getDb()->users, $user_id);

            $this->assertEquals($user['age'], $age);
        }
    }
}