MongoObject – library that simplifies storing PHP objects in Mongo


MongoObject Overview

Today I released MongoObject package. It is a library that simplifies storing and fetching of object to/from Mongo database.

It is available on github: https://github.com/dintel/mongo-object

And it is also available on packagist: https://packagist.org/packages/dintel/mongo-object

Installation

Using composer:

$ composer require dintel/mongo-object

Features

Most important features of MongoObject:

  • Provides base Object class that you can derive and easily save into Mongo database
  • Objects have schema, but underlying data in Mongo can diverge from it
  • Provides Mapper class that allows loading objects from Mongo database
  • Provides MapperFactory class that can be used as a service factory in Zend Framework 2

Object class

Object is a base class that should be used for all classes that should have an ability to be easily saved into Mongo database. Let’s look on following simple example to understand how classes should be derived from Object:

<?php
namespace MongoObjectExample;

use MongoObject\Object;
use MongoCollection;

class User extends Object
    public function __construct(array $data, MongoCollection $collection)
    {
        $schema = [
            '_id' => ['type' => Object::TYPE_ID, 'null' => false],
            'login' => ['type' => Object::TYPE_STRING, 'null' => false],
            'name' => ['type' => Object::TYPE_STRING, 'null' => false],
            'email' => ['type' => Object::TYPE_STRING, 'null' => false],
            'password' => ['type' => Object::TYPE_STRING, 'null' => false, 'hidden' => true],
            'active' => ['type' => Object::TYPE_BOOL, 'null' => false],
            'created' => ['type' => Object::TYPE_DATE, 'null' => false],
        ];
        $defaults = [
            'active' => true,
            'created' => new MongoDate(),
        ];
        parent::__construct($schema, $data + $defaults, $collection, __NAMESPACE__);
    }

    public function checkPassword($password)
    {
        return hash('sha256', $password) === $this->_data['password'];
    }
}

In this example class User is derived from Object and defines schema with following fields:

  • _id - MongoId - Internal ID that is assigned by Mongo
  • login - string that must not be null
  • name - string that must not be null
  • email - string that must not be null
  • password - string that is not accessible as property
  • active - boolean that must not be null
  • created - MongoDate that must not be null

In User constructor we pass object’s schema to Object constructor. This schema is used by Object __get and __set methods to check whether properties are defined and ensure that property value type matches what is defined in schema. Schema is an associative list in which keys are names of fields and values are arrays in which following keys has to be defined:

  • type - one of Object::TYPE_ID, Object::TYPE_BOOL, Object::TYPE_INT, Object::TYPE_DOUBLE, Object::TYPE_STRING, Object::TYPE_ARRAY, Object::TYPE_DATE, Object::TYPE_REFERENCE.
  • null - (optional) true if null value allowed, false otherwise (true by default).
  • hidden - (optional) true if field must not be accessible using __set and __get methods, false otherwise (false by default).

Object stores actual data in $this->data array. If some values are missing there or there is excessive data in it, it is left intacct. This allows changing structure of object as simple as just changing schema and adding default value.

Object has following methods implemented:

  1. __construct(array $schema, array $data, MongoCollection $collection, $modelsNamespace = null) - constructor
  2. __isset($name) - allows checking whether property is defined in schema
  3. __set($name, $value) - sets value of property if it is defined in schema and is not hidden
  4. __get($name) - returns value of property if is defined in schema and is not hidden
  5. save() - saves object into it’s collection
  6. delete() - deletes object from it’s collection, unless it’s _id value is not set
  7. isNew() - checks whether _id value is null
  8. getDBRef() - return Mongo DBRef pointing to this object (or null of object does not have _id)
  9. refresh() - reload object data from Mongo (only if _id value is not null)
  10. jsonSerialize() - returns array of data with _id field renamed to id (used for serializing objects into JSON)
  11. mergeData() - merge properties values from another array
  12. fetchDBRef($collectionName, $typeName, $dbref) - protected helper function for fetching objects using their Mongo DBRef

Most of methods are very simple and do not need additional explanations, except constructor and fetchDBRef. Last parameter of constructor is a namespace in which models (in this case classes that derive from Object class) are defined. MongoObject assumes that all these classes are defined in single namespace and it should be supplied for fetchDBRef function to work correctly. This is because fetchDBRef function may fetch object of class other than one which calls it. In this case it will construct object of that type, but it has to know namespace in which it can be found. In case modelsNamespace is not defined, typeName that is passed to fetchDBRef must be class name including namespace.

Mapper overview

Mapper class simplifies fetching objects from Mongo database. It provides methods to fetch objects, count them (without actually fetching) and even creating new objects (from classes that are derived from Object). For Mapper to work, a few requirements must be met:

  • Classes that are derived from Object must be defined in one single namespace.
  • Classes that are derived from Object must have constructor with following signature - __construct(array $data, MongoCollection $collection)

Mapper methods

Mapper has following method defined:

  • __construct(MongoDB $mongodb, $modelsNamespace = null) - constructor
  • findObject($table, $type, $id) - find object of type $type in Mongo collection $table with _id $id.
  • findObjectByProp($table, $type, $name, $value) - find object of type $type in Mongo collection $table which has field named $name and it’s value is $value.
  • fetchObjects($table, $type, array $selector = [], array $order = null) - find all objects of type $type in Mongo collection $table using Mongo select query $selector and ordered by $order.
  • countObjects($table, $query = array()) - count objects that match Mongo select query $query in collection $table.
  • newObject($table, $type, $data) - construct new object of type $type with property values $data that will be stored in collection $table.

When Mapper is constructed it gets 2 parameters. First parameter is MongoDB in which all objects will be stored. Second is name of PHP namespace in which all class that are derived from Object are defined. Second parameter is needed for convenience - when you later call methods of Mapper, everywhere you have to supply $type parameter you can supply only class name without namespace in which it is declared.

Take into account that all classes that are used with Mapper must have constructor defined with following signature: __construct(array $data, MongoCollection $collection). Another way to ensure that your model classes (classes that derive from Object) are OK to work with Mapper is to implement MongoObject\MongoObject interface.

Using MapperFactory in ZF2

MongoObject provides MapperFactory class that is able to construct and return object of Mapper when using Zend Framework 2 Service Manager. To use it you have to do 2 things:

  • Add MapperFactory factory configuration to ZF2 Service Manager configuration.
  • Add mongoObjectMapper configuration to your ZF2 configuration.

Add MapperFactory to ZF2 Service Manager

For this you have to add in your application configuration (or module configuration) new factory. Here is an example of ZF2 configuration that defines only one factory that is called mongo:

<?php

return array(
    'service_manager' => array(
        'factories' => array(
            'mongo' => 'MongoObject\\MapperFactory',
        ),
    ),
);

Add mongoObjectMapper configuration to ZF2 configuration

Second step is to add mongoObjectMapper configuration key to your ZF2 configuration. This key is used to fetch parameters for establishing connection to Mongo database. Here is an example that is used by phpunit tests of MongoObject:

<?php

return array(
    'service_manager' => array(
        'factories' => array(
            'mongo' => 'MongoObject\\MapperFactory',
        ),
    ),
    'mongoObjectMapper' => array(
        'uri' => 'mongodb://localhost:27017',
        'options' => [],
        'database' => 'test',
        'modelsNamespace' => 'MongoObjectTest',
    ),
);

As you can see mongoObjectMapper has following keys:

  • uri - should contain URI of Mongo database. If omitted then default value of 'mongodb://localhost:27017' is used.
  • options - additional options that are passed to MongoClient when establishing connection to Mongo.
  • database - database in Mongo against which Mapper would work.
  • modelsNamespace - passed to Mapper constructor, see previous section for details.

Once you have everything in place you can use $mapper = $this->getServiceLocator()->get('mongo'); in your ZF2 controller to get Mapper object ready for use.