Home arrow MySQL arrow Building an ORM in PHP: Domain Modeling

Building an ORM in PHP: Domain Modeling

If you feel that the features provided by popular ORM packages like Doctrine 2.x or RedBeanPHP are rather like overkill for handling the relationships between the domain objects that comprise your applications, then take a peek at this tutorial. In a step-by-step fashion, youíll be guided through the development of a simple, extendable ORM, which youíll be able to tweak to suit your personal requirements and needs.

TABLE OF CONTENTS:
  1. Building an ORM in PHP: Domain Modeling
  2. Handling Collections of Entities
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
November 22, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Whatís more, if you've already read the first installment of this tutorial, you're probably familiar with the functionality that I plan to add to this custom ORM. In that first part, I implemented the ORMís data access and mapping layers. And as youíll surely recall, the entire implementation process was pretty straightforward and easy to follow.

Of course, in its current state the ORM is still far from a fully-functional structure. We need to add some additional components to it, such as a domain model and the classes responsible for handling collections of entities (remember that the ORM relies heavily on the data mapper pattern to do its business properly).

To fit this requirement, in the lines to come Iíll be showing you the implementation of these domain classes, thus further extending the ORMís existing functionality.

Meanwhile, if you missed the first part of this series - or need a recap - you can find it here: Building an ORM in PHP.

Extending the ORM's functionality: modeling domain objects

As I explained at the beginning, the next tier I plan to add to the existing structure of this sample ORM is a domain model. Since my purpose here is to demonstrate how to put the ORM into action by deploying a basic blog program, the model will be composed of four classes. The first one will define the structure and behavior of generic entities, while the remaining three will be tasked with modeling blog entries, comments and authors respectively.

Having said that, hereís the first of these classes:

(Blog/Model/AbstractEntity.php)

<?php

namespace BlogModel;
use BlogModelProxy;

abstract class AbstractEntity
{
    protected $_values = array();
    protected $_allowedFields = array();
   
    /**
     * Constructor
     */
    public function __construct(array $fields)
    {
        foreach ($fields as $name => $value) {
            $this->$name = $value;
        }
    }
   
    /**
     * Assign a value to the specified field via the corresponding mutator (if it exists);
     * otherwise, assign the value directly to the '$_values' protected array
     */
    public function __set($name, $value)
    {  
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException('The field ' . $name . ' is not allowed for this entity.'); 
        }
        $mutator = 'set' . ucfirst($name);
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);          
        }
        else {
            $this->_values[$name] = $value;
        }   
    }
   
    /**
     * Get the value assigned to the specified field via the corresponding getter (if it exists);
     * otherwise, get the value directly from the '$_values' protected array
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException('The field ' . $name . ' is not allowed for this entity.');   
        }
        $accessor = 'get' . ucfirst($name);
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;   
        }
        if (!isset($this->_values[$name])) {
            throw new InvalidArgumentException('The field ' . $name . ' has not been set for this entity yet.');
        }
        // if the field is a proxy for an entity, load the entity via its 'load()' method
        $field = $this->_values[$name];
        if ($field instanceof ProxyEntityProxy) {
            $field = $field->load();
        }
        return $field;
    }

    /**
     * Check if the specified field has been assigned to the entity
     */
    public function __isset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException('The field ' . $name . ' is not allowed for this entity.');
        }
        return isset($this->_values[$name]);
    }

    /**
     * Unset the specified field from the entity
     */
    public function __unset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException('The field ' . $name . ' is not allowed for this entity.');
        }
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
            return true;
        }
        return false;
    }
   
    /**
     * Get an associative array with the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }             
}

As seen above, this abstract parent implements, through some magic PHP methods, the logic required to model generic entities. It does nothing more and nothing less. With this base structure up and running, itís time to define the subclasses that spawn the aforementioned blog entries, comments and authors. Here they are:

(Blog/Model/Entry.php)

<?php

namespace BlogModel;
use BlogModelProxy;

class Entry extends AbstractEntity
{
    protected $_allowedFields = array(
        'id',
        'title',
        'content',
        'comments'
    );

    /**
     * Set the entry ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
            throw new InvalidArgumentException('The entry ID is invalid.');
        }
        $this->_values['id'] = $id;
    }

    /**
     * Set the entry title
     */
    public function setTitle($title)
    {
        if (!is_string($title) || strlen($title) < 2 || strlen($title) > 32) {
            throw new InvalidArgumentException('The title of the entry is invalid.');
        }
        $this->_values['title'] = $title;
    }

    /**
     * Set entry content
     */
    public function setContent($content)
    {
        if (!is_string($content) || empty($content)) {
            throw new InvalidArgumentException('The entry is invalid.');
        }
        $this->_values['content'] = $content;
    }

    /**
     * Set the comments for the blog entry
     * (assigns a Collection Proxy for lazy-loading comments)
     */
    public function setComments(ProxyCollectionProxy $comments)
    {
        $this->_values['comments'] = $comments;
    }
}

 

(Blog/Model/Comment.php)

<?php

namespace BlogModel;
use BlogModelProxy;

class Comment extends AbstractEntity
{
    protected $_allowedFields = array(
        'id',
        'content',
        'author',
        'author_id',
        'entry_id'
    );

    /**
     * Set the comment ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
            throw new InvalidArgumentException('The comment ID is invalid.');
        }
        $this->_values['id'] = $id;
    }

    /**
     * Set the content for the comment
     */
    public function setContent($content)
    {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException('The comment is invalid.');
        }
        $this->_values['content'] = $content;
    }

    /**
     * Set the author of the comment
     * (assigns an entity proxy for lazy-loading authors)
     */
    public function setAuthor(ProxyEntityProxy $author)
    {
        $this->_values['author'] = $author;
    }
}

 

(Blog/Model/Author.php)

<?php

namespace BlogModel;

class Author extends AbstractEntity
{
    protected $_allowedFields = array(
        'id',
        'name',
        'email'
    );

    /**
     * Set the author ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
            throw new InvalidArgumentException('The ID of the author is invalid.');
        }
        $this->_values['id'] = $id;
    }

    /**
     * Set the author's name
     */
    public function setName($name)
    {
        if (!is_string($name) || strlen($name) < 2) {
            throw new InvalidArgumentException('The name of the author is invalid.');
        }
        $this->_values['name'] = $name;
    }

    /**
     * Set the author's email
     */
    public function setEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('The email address of the author is invalid.');
        }
        $this->_values['email'] = $email;
    }
}

Definitely, thereís no much that can be said about the above domain classes. They simply implement a few mutators, which come in handy for assigning values (with some constraints, naturally) to the fields of the corresponding entities. Quite possibly, the most interesting detail to note here is the signatures of the ďsetComments()Ē and ďsetAuthor()Ē methods. These accept proxies instead of real domain objects, as both comments and authors will be lazy-loaded from the database.

Anyway, donít worry if you still donít understand how the proxies will fit into the ORMís structure, as they will be covered in detail in the next part of this tutorial.

So far, so good. With the implementation of the earlier classes, I managed to create a basic domain model, which can be easily extended further to incorporate a few other entities. So, what's next? Well, since the ORM must be capable of handling one-to-many relationships (like the one that exists between entries and the related comments), itís necessary to create a class capable of handling collections of entities.

Thatís exactly what Iíll be doing in the following segment. Therefore, click on the link that appears below and keep reading. 



 
 
>>> More MySQL Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

MYSQL ARTICLES

- Oracle Unveils MySQL 5.6
- MySQL Vulnerabilities Threaten Databases
- MySQL Cloud Options Expand with Google Cloud...
- MySQL 5.6 Prepped to Handle Demanding Web Use
- ScaleBase Service Virtualizes MySQL Databases
- Oracle Unveils MySQL Conversion Tools
- Akiban Opens Database Software for MySQL Use...
- Oracle Fixes MySQL Bug
- MySQL Databases Vulnerable to Password Hack
- MySQL: Overview of the ALTER TABLE Statement
- MySQL: How to Use the GRANT Statement
- MySQL: Creating, Listing, and Removing Datab...
- MySQL: Create, Show, and Describe Database T...
- MySQL Data and Table Types
- McAfee Releases Audit Plugin for MySQL Users

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: