Home arrow PHP arrow PHP Proxy Patterns

PHP Proxy Patterns

In this first part of a three-part tutorial, I introduce you to what proxy objects are and how they can be used for lazy-loading domain objects in PHP. In this case, I will show you how to use proxies in the development of a blog application, to fetch on request the comments associated with a given post.

TABLE OF CONTENTS:
  1. PHP Proxy Patterns
  2. Implementing the blog's data access layer
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
October 24, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

There's no doubt that the most challenging facet in the development of an MVC-based web application, even the simplest one, is the implementation of the model(s). While controllers and views are inherently relevant pieces within the pattern's schema, they can be created to display a more relaxed behavior (at least to some extent), and even work decently upon a poorly-designed infrastructure, be it a full framework or a set of independent libraries.

On the other hand, the model is a pristine and carefully-crafted creature, responsible for the application's state. Since it plays such a crucial role, it must be conceived from the very beginning to follow well-defined business rules and constraints. These rules and constraints must naturally be independent of the underlying storage mechanism.

Despite this, sooner or later the model must be persisted in some form. At first glance, this looks like a fairly straightforward process. In reality, however, it presents some notorious difficulties, especially when it's necessary to deal with a batch of interrelated domain objects (or aggregated roots, in Domain-Driven Design's jargon).

Most of the time, the persistence layer sits on a RDBMS (even though it might be a document-oriented database such as MongoDB). This point implies that retrieving a bunch of related domain objects can be a very expensive and sometimes unnecessary task. Consider the typical blog posts/comments relationship, and you'll clearly see my point.

Of course, this common issue can be easily addressed via lazy-loading. You're probably familiar with this approach, and know that it can be implemented in all sorts of forms and flavors. However, in domain models composed of a few entities which have a certain number of restrictions, lazy-loading can be a bit harder to implement than usual, because normally the piece connecting the domain and data access layers is a set of data mappers.

The functionality of a data mapper is built in most cases around a group of generic finders and a few other complementary methods that perform CRUD operations in the database. It's somewhat difficult to make a mapper differentiate between records that should be lazy-loaded and records that shouldn't. Fortunately, the logic required to achieve this can be neatly encapsulated behind a proxy object -- an object that acts like a placeholder for the actual domain object(s). In fact, lazy-loading is just one use case where the implementation of proxy objects can be of great help; they can also be used in other  situations, such as controlling other objects, unit testing and a few more.

In this tutorial, I'll show you how to exploit the functionality of proxies to lazy-load the domain objects of a basic -- yet extendable -- PHP blog application. Keep in mind that the program will be built in a step-by-step fashion, due to the extensive number of sample classes that I plan to define from this point onward. With that said, bear with me and be ready to learn how to create proxy objects in PHP.

Now, let's get going!         

Taking the first step: building the blog's domain model

As I said in the introduction, before you see proxy objects lazy-loading records from a blog's database,  it's necessary to create the layers upon which the application will be seated.

The first layer that I plan to set up is the domain model. It will be composed of a few blog entries and their associated comments. Here's the abstract class that I'll be using later on for modeling the aforementioned entities:  

(MyApp/Model/AbstractEntity.php)

<?php

namespace MyAppModel;

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' array
     */
    public function __set($name, $value)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException("Setting 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 of the specified field (via the getter if it exists or by getting it from the $_values array)
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException("Getting 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])) {
            return $this->_values[$name];  
        }
        throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");   
    }
         
    /**
     * 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("Unsetting the field '$name' is not allowed for this entity.");
        }
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
            return true;
        }
        throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");
    }

    /**
     * Get an associative array with the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }   
}

Even though the implementation of the above "AbstractEntity" class seems to be rather complex at first glance, this is a misleading impression. In reality, this class only defines the structure and constraints of generic entities, which can be assigned and removed properties via the magic "__set()", "__get()" and "__unset()" PHP methods (or through the corresponding mutators/getters if they've been explicitly implemented). 

With this abstract parent already set, the next step is to create a couple of subclasses, responsible for generating blog entries and comments.

The class below is the blueprint for the former:
 
(MyApp/Model/Entry.php)

<?php

namespace MyAppModel;
use MyAppCommon;

class Entry extends AbstractEntity
{  
    protected $_allowedFields = array('id', 'title', 'content', 'author', 'comments');
   
    /**
     * Set the entry ID
     */
    public function setId($id)
    {
        if (!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 999999)))) {
            throw new InvalidArgumentException('The entry ID is invalid.');
        }
        $this->_values['id'] = $id;
    }
   
    /**
     * Set the title of the entry
     */ 
    public function setTitle($title)
    {
        if (!is_string($title) || strlen($title) < 2 || strlen($title) > 64) {
            throw new InvalidArgumentException('The title of the entry is invalid.');
        }
        $this->_values['title'] = $title;
    }
   
    /**
     * Set the content of the entry
     */
    public function setContent($content)
    {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException('The entry is invalid.');
        }
        $this->_values['content'] = $content;
    }
   
    /**
     * Set the author of the entry
     */
    public function setAuthor($author)
    {
        if (!is_string($author) || strlen($author) < 2 || strlen($author) > 64) {
            throw new InvalidArgumentException('The author of the entry is invalid.');
        }
        $this->_values['author'] = $author;
    }
   
    /**
     * Set the comments of the entry (assigns a collection proxy for lazy-loading comments)
     */
    public function setComments(CommonCollectionProxy $comments)
    {
        $this->_values['comments'] = $comments;
    }                                                                         
}

As you can see above, the "Entry" subclass is nothing but a refined implementation of its abstract parent, which assigns some typical fields to a blog entry via a bunch of mutators, including an ID, the title, content, author and the related comments. While this is fairly easy to digest, you should pay attention to the signature of the class' "setComments()" method. It deserves special analysis: effectively, instead of taking something more banal like a plain string, the method accepts a collection proxy object.

Even though the class that spawns this kind of object hasn't been defined yet, the method should give you a clearer idea of how a proxy can be used for lazy-loading database records. In this case, the proxy is used as a mediator (or a stand-in) for the actual comment objects, which encapsulates all the logic required for fetching them on request from the storage layer.

But let's slow down a second and not get ahead of ourselves, as the implementation of the corresponding proxy classes will be discussed in depth in the next tutorial. So, for the moment take a look at the following domain class. It's tasked with modeling the aforementioned comment objects:      

(MyApp/Model/Comment.php)

<?php

namespace MyAppModel;

class Comment extends AbstractEntity
{  
    protected $_allowedFields = array('id', 'content', 'author', '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' => 999999)))) {
            throw new InvalidArgumentException('The comment ID is invalid.');
        }
        $this->_values['id'] = $id;       
    }
   
    /**
     * Set the comment content
     */
    public function setContent($content)
    {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException('The comment is invalid.');
        }
        $this->_values['content'] = $content;
    }
   
    /**
     * Set the comment author
     */
    public function setAuthor($author)
    {
        if (!is_string($author) || strlen($author) < 2 || strlen($author) > 64) {
            throw new InvalidArgumentException('The author of the comment is invalid.');
        }
        $this->_values['author'] = $author;
    }                                                                    
}

Admittedly, this class is a no-brainer; all of its functionality is focused on modeling the structure and constraints of blog post comments. Similar to its counterpart "Entry," the "Comment" class uses a few mutators for setting the ID, content and author of a comment, a process that's a breeze to grasp.

So far, so good. With the implementation of this last subclass, the domain model corresponding to this sample blog application is finally up and running. Therefore, the next step is to set up the blog's data access layer, which will be charged with persisting the pertinent model in a couple of MySQL tables. 

This brand new layer will be covered in the next segment, so click the link that appears below and keep reading.



 
 
>>> More PHP Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: