Search This Blog

4/22/2011

How to create an ORM layer

Last post was about the concept of ORM. This post is about what we expect in an ORM layer and some tips of how to implement an ORM layer by yourself.

One of the expected results of an ORM layer is that it does not trigger rework. One kind of rework in traditional applications is: create the database structure and, then, create classes that represents that tables. Last post I sad that DB and classes are similar. As the class attributes are similar to the table columns, why we should specify them two times? And, if we need to change in one place, we should change in the other place. The solution is to use "reflection" in one side and then aply the rules in the other side (I will explain).

There are two possibilities:

  1. The ORM layer may read the table structure and make their attributes dinamically; or
  2. The ORM layer may read the classes structure and make the database structure dinamically.

Both are good solutions. The first, to be more optimized, should read the DB structure once and make the classes dinamically (dinamic generation of source-code). The second should "read" the classes structure once and make the DB structure (during the application installation, for example).

PHP offers some interesting tools to ORM creation. The first of them is the possibilidy of creation of "virtual" attributes, that are accessed by magic methods. The second is to use the Reflection extension, that gets information about source-code in execution time. Let's try to make a source-code with abstract point that ilustrates this situation:

/**
 * Base class for all entity classes
 */
abstract class Entity {

    /**
     * Array of attribute values
     * @var array[string => mixed] Array indexed by the attribute name
     *                             and points to the attribute value
     */
    private $values = array();

    /**
     * Attribute that has the entity definition
     * @var EntityDefinition
     */
    private $entityDefinition = null;

    /**
     * Method that returns the entity definition of the class
     * @return EntityDefinition
     */
    abstract public function getEntityDefinition();

    /**
     * Constructor: create an empty entity
     */
    final public function __construct() {
        $this->entityDefinition = $this->getEntityDefinition();
        foreach ($this->entityDefinition->getAttributes() as $attribute) {
            $this->values[$attribute->getName()] = $attribute->getEmptyValue();
        }
    }

    /**
     * Gets the value of a virtual attribute
     * @param string $attribute_name Name of the attribute to be returned
     * @param mixed Value of the attribute
     */
    final public function __get($attribute_name) {
        if ($this->entityDefinition->hasAttribute($attribute_name)) {
            return $this->values[$attribute_name];
        }
    }

    /**
     * Sets the new value of a virtual attribute
     * @param string $attribute_name Name of attribute to be updated
     * @param mixed $value New value of the attribute
     * @return bool Whether the attribute was set or not
     */
    final public function __set($attribute_name, $value) {
        if (!$this->entityDefinition->hasAttribute($attribute_name)) {
            return false;
        }
        return $this->entityDefinition->getAttribute($attribute_name)->setValue(
            $this->values[$attribute_name],
            $value
        );
    }

    // Other methods...
}

/**
 * Entity class "user"
 */
class User extends Entity {

    /**
     * {@inheritdoc}
     */
    public function getEntityDefinition() {
        return new EntityDefinition('./definition/user.xml');
    }
}

The example above I invented now, but exemplifies the concept of ORM. The first class is the base class to the creation of all entities (I named "Entity"). It defines a standard constructor that keeps the "entity definition" in an attribute and sets empty values to each attribute of instance ($this). Then, was implemented __get and __set magic methods, that checks the entity definition and makes what they should as generic way. Note that I did not use the Reflection extension. Instead, I obliged that child classes of "Entity" should implement the "getEntityDefinition" method, that returns the definition of the entity (what the Reflection extension could do, but with limitations).

See in the second class (the "User" class) that was not necessary to write so much. Only one method that returns the entity definition of the "User" entity. One way to do that is keeping this information in an XML file. The JPA framework (for Java) keeps definitions in XML. This way, the definition is readable by the application, humans and other applications. Other alternative is to set entity definition by source-code, using a class that keeps that.

In case of keeping definition in XML, it could be good have a method that converts the XML string into an application object. In the example above, it is represented by the "EntityDefinition" class, that I did not implement here. This class is the main piece of ORM layer, since it offers many useful methods for the entity class (like "hasAttribute", for example).

Well, summarizing: the "Entity" and the "EntityDefinition" could be the support classes of the ORM layer. They could be offered by third part libraries. What the programmer might know is to create the entity classes and their definitions in XML. For that, it is important to know well how the base class works.

With this, note that instantiating an object of "User" class, we have generic getters and setters ready to use. I did not implement the more complex part of ORM, but let's take it for an other post.

Use the getters and setters is simple in PHP:

$u = new User();
$u->username = 'rubens';  // calls the method: __set('username', 'rubens')
echo $u->username;        // calls the method: __get('username')

And that is all for a development of a Relational Database/Object Oriented framework.

No comments:

Post a Comment