Multiple levels of Embedded Documents in MongoDB post

Posted on 2010-07-27 by jwage


One of the greatest things about MongoDB is the fact that it is schema-less. It makes for a very flexible domain model persistence layer. For example it is possible to have multiple levels of embedded documents. A useful example might be where you have many profiles and each profile has many addresses. In the Doctrine MongoDB ODM mapping this is trivial.

First create your top level User document:

/** @Document(collection="users") */
class User
{
    /** @Id */
    private $id;

    /** @String */
    private $username;

    /** @EmbedMany(targetDocument="Profile") */
    private $profiles = array();

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

    public function addProfile(Profile $profile)
    {
        $this->profiles[] = $profile;
    }
}

As you can see we embed another document class named Profile so lets define that as an embedded document:

/** @EmbeddedDocument */
class Profile
{
    /** @String */
    private $name;

    /** @EmbedMany(targetDocument="Address") */
    private $addresses = array();

    public function setName($name)
    {
        $this->name = $name;
    }

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

Finally, we’ve embedded a document in Profile named Address so lets define it:

/** @EmbeddedDocument */
class Address
{
    /** @String */
    private $number;

    /** @String */
    private $street;

    /** @String */
    private $city;

    /** @String */
    private $state;

    /** @String */
    private $zipcode;

    public function setNumber($number)
    {
        $this->number = $number;
    }

    public function setStreet($street)
    {
        $this->street = $street;
    }

    public function setCity($city)
    {
        $this->city = $city;
    }

    public function setState($state)
    {
        $this->state = $state;
    }

    public function setZipcode($zipcode)
    {
        $this->zipcode = $zipcode;
    }
}

Now you can start working with the PHP objects just like you would if no persistence layer was present at all and persist the objects transparently when you are ready to have the state of the objects managed by Doctrine:

$user = new User();
$user->setUsername('jwage');

$profile = new Profile();
$profile->setName('Profile #1');

$user->addProfile($profile);

$address = new Address();
$address->setNumber('6512');
$address->setStreet('Mercomatic');
$address->setCity('Nashville');
$address->setState('Tennessee');
$address->setZipcode('37209');

$profile->addAddress($address);

$profile = new Profile();
$profile->setName('Profile #2');

$user->addProfile($profile);

$address = new Address();
$address->setNumber('475');
$address->setStreet('Buckhead Ave');
$address->setCity('Atlanta');
$address->setState('Georgia');
$address->setZipcode('30303');

$profile->addAddress($address);

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

The above would result in an array being stored in MongoDB like the following:

Array
(
    [_id] => MongoId Object
        (
        )
    [username] => jwage
    [profiles] => Array
        (
            [0] => Array
                (
                    [name] => Profile #1
                    [addresses] => Array
                        (
                            [0] => Array
                                (
                                    [number] => 6512
                                    [street] => Mercomatic
                                    [city] => Nashville
                                    [state] => Tennessee
                                    [zipcode] => 37209
                                )
                        )
                )
            [1] => Array
                (
                    [name] => Profile #2
                    [addresses] => Array
                        (
                            [0] => Array
                                (
                                    [number] => 475
                                    [street] => Buckhead Ave
                                    [city] => Atlanta
                                    [state] => Georgia
                                    [zipcode] => 30303
                                )
                        )
                )
        )
)

We can then later retrieve the documents from MongoDB and our object domain model will be reconstructed as you have mapped it:

$user = $dm->findOne('User', array('username' => 'jwage'));

You can see the complete working script for this blog post as a gist on github.

Categories: articles