Category Archives: MongoDB

Multiple class inheritance with Doctrine ODM in Symfony: One collection for multiple document types

When you use Doctrine ORM in Symfony all the data is stored in tables having every row the same columns. Storing different type of entities in the same table is not posible (AFAIK). But MongoDB can store any kind of document in the same collection making possible to have different kind of objects in the same place and thus making inheritance easy to use if you need it.

doctrine-odm-mongodb

In this example I’m going to create the documents needed to reflect an RFID system (from a real application I did sometime ago) where cards, called internally tag, can represent different things: a person, an item, an event, etc. Thus our mongodb database will have a collection of tags with documents of type person, item, event, etc.

Each time a card is used the system will know what it represents using the internal EPC of it: as you may be thinking this will be our ID.

Install MongoDB, the PHP driver and create a new database

This process depends on the operating system you use. Here are the instructions for some of them in older posts:

To create a new database in our mongodb server you have just to make a use statement and create a new collection in it with the mongo client:

> use app
switched to db app
> db.createCollection('tags')
{ "ok" : 1 }

Install MongoDB Doctrine library and DoctrineMongoDBBundle

This process is needed to be able to use MongoDB within the Symfony project. Those steps are extracted from the Symfony DoctrineMongoDBBundle Documentation.

Add the MongoDB Doctrine library and the DoctrineMongoDBBundle to composer.json:

{
    "require": {
        "doctrine/mongodb-odm": "~1.0",
        "doctrine/mongodb-odm-bundle": "~3.0"
    },
}

Now install them using composer. Using a local installation of it:

$ php composer.phar update doctrine/mongodb-odm doctrine/mongodb-odm-bundle

Or a global installation:

$ composer update doctrine/mongodb-odm doctrine/mongodb-odm-bundle

Register the annotations library by adding the following to the autoloader (below the existing AnnotationRegistry::registerLoader line):

use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
AnnotationDriver::registerAnnotationClasses();

It should look something like this:

<?php
// app/autoload.php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
use Composer\Autoload\ClassLoader;

/**
 * @var ClassLoader $loader
 */
$loader = require __DIR__.'/../vendor/autoload.php';

AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
AnnotationDriver::registerAnnotationClasses();

return $loader;

Now update AppKernel.php:

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle(),
    );

    // ...
}

Configure MongoDB ODM

Symfony must know how to connect to our mongodb instance. This is configured in app/config/config.yml being the easiest way using the auto_mapping option:

# app/config/config.yml
doctrine_mongodb:
    connections:
        default:
            server: mongodb://localhost:27017
            options: {}
    default_database: test_database
    document_managers:
        default:
            auto_mapping: true

I like using the app/config/parameters.yml to set up the host, port and database name:

# app/config/parameters.yml
parameters:
    mongodb_host: 127.0.0.1
    mongodb_port: 27017
    mongodb_name: app
# app/config/config.yml
imports:
    - { resource: parameters.yml }

doctrine_mongodb:
    connections:
        default:
            server: mongodb://%mongodb_host%:%mongodb_port%
            options: {}
    default_database: %mongodb_name%
    document_managers:
        default:
            auto_mapping: true

The document classes

Now is when we start coding. I’ll suppose you already have an AppBundle created (if not look here at the Symfony Best Practices – Creating the project).

The “parent” class: Single Collection Inheritance

All the different things we want to represent as a document in mongodb in the same collection and an object in PHP must inherit from the same class called tag. The “magic” here is the @MongoDB\InheritanceType("SINGLE_COLLECTION") annotation. This will tell MongoDB ODM that this collection will store different documents and a discrimination field will tell each other apart.

<?php
// src/AppBundle/Document/TagInterface.php

namespace AppBundle\Document;

interface TagInterface
{
}
<?php
// src/AppBundle/Document/Tag.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document(collection="tags")
 * @MongoDB\InheritanceType("SINGLE_COLLECTION")
 * @MongoDB\DiscriminatorField("type")
 * @MongoDB\DiscriminatorMap({"person"="Person", "item"="Item"})
 */
class Tag implements TagInterface
{
    /**
     * @MongoDB\Id(strategy="NONE")
     */
    protected $epc;

    /**
     * Set epc
     *
     * @param custom_id $epc
     * @return self
     */
    public function setEpc($epc)
    {
        $this->epc = $epc;
    }

    /**
     * Get epc
     *
     * @return custom_id $epc
     */
    public function getEpc()
    {
        return $this->epc;
    }
}

The inherited classes: extends

Now that we have the parent class the only thing we need to store documents in that collection is extend it:

<?php
// src/AppBundle/Document/Person.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document
 */
class Person extends Tag
{
    /**
     * @MongoDB\String
     */
    protected $name;

    /**
     * Set name
     *
     * @param string $name
     * @return self
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string $name
     */
    public function getName()
    {
        return $this->name;
    }
}
<?php
// src/AppBundle/Document/Item.php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document
 */
class Item extends Tag
{
    /**
     * @MongoDB\Float
     */
    protected $price;

    /**
     * Set price
     *
     * @param float $price
     * @return self
     */
    public function setPrice($price)
    {
        $this->price = $price;

        return $this;
    }

    /**
     * Get price
     *
     * @return float $price
     */
    public function getPrice()
    {
        return $this->price;
    }
}

Store and read documents to/from the collection

All the configuration and classes are done, we can start persisting and finding documents anywhere in the project: a controller, a command, etc. For example lets persist and find some Person and Item objects in a controller:

<?php
// src/AppBundle/Controller/TagsController.php

namespace AppBundle\Controller;

use AppBundle\Document\Item;
use AppBundle\Document\Person;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class TagsController extends Controller
{
    /**
     * @Route("/persist", name="app_persist")
     * @param Request $request The Request object
     * @return Response A Response instance
     */
    public function persistAction()
    {
        $item = new Item();
        $item->setEpc('0000000000000000000000AA');
        $item->setPrice('19.99');

        $person = new Person();
        $item->setEpc('0000000000000000000000AB');
        $item->setName('Eneko');

        $dm = $this->get('doctrine_mongodb')->getManager();
        $dm->persist($item);
        $dm->persist($person);
        $dm->flush();

        return new Response('Created tags');
    }

    /**
     * @Route("/find", name="app_find")
     * @param Request $request The Request object
     * @return Response A Response instance
     */
    public function findAction(Request $request)
    {
        $epc = $request->request->get('epc');
        $tag = $this->get('doctrine_mongodb')
            ->getRepository('AppBundle:Tag')
            ->find($epc);

        if (!$tag) {
            throw $this->createNotFoundException('No tag found for epc '.$epc);
        }

        switch(true) {
            case $tag instanceof Person:
                $type = 'Person';
                break;
            case $tag instanceof Item:
                $type = 'Item';
                break;
        }

        return new Response('Found '.$type);
    }

    /**
     * @Route(
     *     "/find-with-param-converter/{epc}",
     *     requirements={
     *         "epc": "[a-zA-Z0-9]{24}"
     *     },
     *     name="app_find_with_param_converter"
     * )
     * @ParamConverter("tag", class="AppBundle\Document\Tag")
     * @param Request $request The Request object
     * @param TagInterface $tag A object of class Tag or one of its children
     * @return Response A Response instance
     */
    public function findWithParamConverterAction(Request $request, TagInterface $tag)
    {
        switch(true) {
            case $tag instanceof Person:
                $type = 'Person';
                break;
            case $tag instanceof Item:
                $type = 'Item';
                break;
        }

        return new Response('Found '.$type);
    }
}

And how are all those object stored in mongodb and how can Doctrine know what class each one represents? That’s configured in the parent class with the @MongoDB\DiscriminatorField and the @MongoDB\DiscriminatorMap annotations. Each document simply has a field named type with the name of the class on it:

> db.tags.find()
{
    "_id" : "0000000000000000000000AA",
    "type" : "item",
    "price" : 19.99
},
{
    "_id" : "0000000000000000000000AB",
    "type" : "person",
    "name" : "Eneko"
}

Ref: http://doctrine-mongodb-odm.readthedocs.org/en/latest/reference/basic-mapping.html
http://doctrine-mongodb-odm.readthedocs.org/en/latest/reference/inheritance-mapping.html
http://jwage.com/post/30490180105/inheritance-and-mapped-super-classes-in-doctrine

Install MongoDB and the PHP driver in CentOS

Install MongoDB server

Create the /etc/yum.repos.d/mongodb-org-3.0.repo file with this content:

[mongodb-org-3.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.0/x86_64/
gpgcheck=0
enabled=1

Then install mongodb with this commando:

# yum install -y mongodb-org

This are the important files and folders for MongoDB in CentOS:

  • Configuration file: /etc/mongod.conf
  • Data files: /var/lib/mongo/
  • Logs files: /var/log/mongodb/

By default the used port is 27017 and also you can ONLY make connections to it within the localhost or 127.0.0.1. If you want to change that you have to edit the bind_ip option:

bind_ip=127.0.0.1

To start the server run this command:

# service mongod start

To stop the server run this command:

# service mongod stop

If you want the server to start on boot:

# chkconfig mongod on

Once the service is running we can connect to it with the mongo command:

$ mongo
MongoDB shell version: 3.0.6
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
	http://docs.mongodb.org/
Questions? Try the support group
	http://groups.google.com/group/mongodb-user
>

Install the PHP driver

Install the required dependencies and the mongo PHP driver itself:

# yum -y install gcc openssl-devel php-devel php-cli php-pear
# pecl install mongo

Note: the library will be installed in /usr/lib64/php/modules/mongo.so

Activate it in /etc/php.ini or in it’s own file in /etc/php.d/30-mongo.ini:

extension=mongo.so

And finally restart httpd:

# service httpd restart

mongodb-logo

Ref: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-red-hat/
http://docs.mongodb.org/ecosystem/drivers/php/

Shrink MongoDB journal files

Trying to get some space in my harddrive I noticed that the journal files of MongoDB where around 3GB.

mongo journal

Fixing this is as simple as stoping MongoDB server, adding the option smallfiles = true in /etc/mongodb.conf file, deleting the journal files and the starting the MongoDB service again.

sudo service mongodb stop
sudo vi /etc/mongodb.conf

At this moment you have to add this to /etc/mongodb.conf:

smallfiles = true

Now just delete the old journal files and restart the service. New journal files will be created:

sudo rm -rf /var/lib/mongodb/journal/*
sudo service mongodb start

mongo journal after

Ref: http://lostquery.com/questions/313/how-do-i-shrink-the-mongodb-journal-files

“WARNING: soft rlimits too low” in MongoDB with Mac OS X

If you get this warning when you connect to mongo shell in Mac OS X:

** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000

A simple way to fix this is setting the limit just before starting mongod with this:

ulimit -n 1024 && mongod

Or this:

launchctl limit maxfiles 1024 1024

But if you are running mongo in a development environment this shouldn’t be a problem, you can just ignore it.

This is a temporary and not very pretty fix. To make this permanent you have to add this to the /etc/launchd.conf file:

launchctl limit maxfiles 1024 1024

Now reboot to make changes effective.

You can see the actual values of the limits running this:

$ launchctl limit maxfiles
        maxfiles    1024           1024

Remember to restart your mongod process every time you change any value to see if it works.

Solve “mongodb cannot be built while v8 is active” in MacPorts

While updating all the ports of macports I got this error:

--->  Configuring mongodb
Error: mongodb cannot be built while v8 is active.
Error: Please deactivate v8 and try again.
Error: You can reactivate v8 again later.
Error: org.macports.configure for port mongodb returned: v8 is active

To deactivate v8 run:

sudo port -f deactivate v8

Run the update process again:

sudo port upgrade outdated

And finally activate v8 again:

sudo port activate v8

mongodb-logo

Ref: http://widgetbook.blogspot.com.es/2013/08/mongodb-and-macports-and-v8.html