Home arrow PHP arrow Page 2 - PHP Proxy Patterns: Collections and Classes

Building a couple of proxy classes - PHP

In this second part of a three-part tutorial, I add to the existing structure of our sample blog program a host of useful components. These include the classes that handle collections of entities and views, and most importantly, the ones responsible for lazy-loading domain objects from the database.

TABLE OF CONTENTS:
  1. PHP Proxy Patterns: Collections and Classes
  2. Building
By: Alejandro Gervasio
Rating: starstarstarstarstar / 0
October 24, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

As with many other aspects of OOP, there are several ways to create proxies that lazy-load data from the persistence layer. In this case, though, I’m going to following an approach similar to the one taken by Ben Scholzen, which uses a specific mapper to get a collection of domain objects on request.

On the other hand, my own take is a bit more extendable. It makes the proxy class an implementer of the following interface:  

(MyApp/Common/LoadableInterface.php)

<?php

namespace MyApp\Common;

interface LoadableInterface
{
    public function load(); 
}

Having such a generic contract at our disposal, it’s really simple to create different proxy classes that lazy-load data from storage, as long as they implement the “LoadableInterface.” But, since I want to enable the blog to lazy-load single entities and collections, the next step is to define an abstract proxy that encapsulates common functionality for both use cases.

The following class does precisely that:  

(MyApp/Common/AbstractProxy.php)

<?php

namespace MyApp\Common;
use MyApp\Model\Mapper;

abstract class AbstractProxy
{
    protected $_mapper;
    protected $_params;
   
    /**
     * Constructor
     */
    public function __construct(Mapper\AbstractMapper $mapper, $params)
    {
        if (!is_string($params) || empty($params)) {
            throw new \InvalidArgumentException('The mapper parameters are invalid.');
        }
        $this->_mapper = $mapper;
        $this->_params = $params; 
    }      
}

Given that this abstract proxy injects a mapper into the constructor, along with the corresponding arguments, building a concrete proxy that lazy-loads a single entity from the persistence layer is this simple:

(MyApp/Common/EntityProxy.php)

<?php

namespace MyApp\Common;
use MyApp\Model;

class EntityProxy extends AbstractProxy implements LoadableInterface
{
    protected $_entity;
   
    /**
     * Load an entity via the 'findById()' method of the injected mapper
     */
    public function load()
    {
        if ($this->_entity === null) {
            $this->_entity = $this->_mapper->findById($this->_params);
            if (!$this->_entity instanceof Model\AbstractEntity) {
                throw new \RunTimeException('Unable to load the specified entity.');
            }
        }
        return $this->_entity;
    }  
}

As you can see, the “EntityProxy” class uses the “findById()” method of the injected mapper (remember that one?) to fetch an entity from the database. Of course, this process is executed whenever its “load()” method is invoked, which allows it to easily fetch a single domain object on request. Pretty good, isn’t it?

What’s more, if you found it easy to understand how the earlier proxy class does its thing, be sure to check the following one. It performs a similar task with a collection of entities:

(MyApp/Common/CollectionProxy.php)

<?php

namespace MyApp\Common;

class CollectionProxy extends AbstractProxy implements LoadableInterface, \Countable, \IteratorAggregate
{
    protected $_collection;
   
    /**
     * Load explicitly a collection of entities via the 'find()' method of the injected mapper
     */
    public function load()
    {
        if ($this->_collection === null) {
            $this->_collection = $this->_mapper->find($this->_params);
            if (!$this->_collection instanceof EntityCollection) {
                throw new \RunTimeException('Unable to load the specified collection.');
            }
        }
        return $this->_collection;
    }
   
    /**
     * Count the entities in the collection after lazy-loading them
     */
    public function count()
    {
        return count($this->load());
    } 
   
    /**
     * Load a collection of entities via the 'find()' method of the injected mapper
     * when called within a 'foreach' construct
     */
    public function getIterator()
    {
        return $this->load();
    }   
}

There you have it. As you can see, the above “CollectionProxy” class uses the “find()” method of the injected mapper to lazy-load a collection of domain objects from storage. This process can be explicitly triggered either by calling the class’s “load()” method, or transparently when an instance of the class is used within a “foreach” construct (or when counting the elements stored in the collection).

So far, so good. At this point, you should have a clearer idea of how to the implementation of a couple of proxy classes can help improve the way that a domain model is persisted. But, before you see these classes into action, it’s necessary to add other components to the blog, including a module that handles views.

This is exactly what I’ll be doing in the following section.

Defining the blog’s landing page: building a view class

Since I want to keep the blog’s source code clear and easy to follow, the layer responsible for manipulating views will be simple. It will be composed of only a single class and an HTML template containing interspersed PHP code.

If you want to see this view handling class, check it  below:

(MyApp/Views/View.php)

<?php

namespace MyApp\Views;

class View
{
    protected $_values = array();
    protected $_templateDirectory = 'Templates';
    protected $_templateFile = 'default_template.php';

    /**
     * Constructor
     */
    public function __construct(array $templateOptions = array())
    {
        // Set the view template directory
        if (isset($templateOptions['templateDirectory'])) {
            $this->setTemplateDirectory($templateOptions['templateDirectory']);
        }
        // Set the view template file
        if (isset($templateOptions['templateFile'])) {
            $this->setTemplateFile($templateOptions['templateFile']);
        }
    }

    /**
     * Set the view template directory
     */
    public function setTemplateDirectory($templateDirectory)
    {
        if (!is_dir($templateDirectory)) {
            throw new \InvalidArgumentException("The template directory '$templateDirectory' is invalid.");
        }
        $this->_templateDirectory = $templateDirectory;   
    }

    /**
     * Get the template directory
     */
    public function getTemplateDirectory()
    {
        return $this->_templateDirectory;
    }
     
    /**
     * Set the view template file
     */
    public function setTemplateFile($templateFile)
    {
        $templateFile = $this->_templateDirectory . DIRECTORY_SEPARATOR . $templateFile;
        if (!file_exists($templateFile) || !is_readable($templateFile)) {
            throw new \InvalidArgumentException("The template file '$templateFile' is invalid.");
        }
        $this->_templateFile = $templateFile;
    }
     
    /**
     * Get the view template file
     */
    public function getTemplateFile()
    {
        return $this->_templateFile;
    }

    /**
     * Assign a value to the specified field of the view via the corresponding mutator (if it exists);
     * otherwise, assign the value directly to the '$_values' protected array
     */
    public function __set($name, $value)
    {
        $mutator = 'set' . ucfirst(strtolower($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 of the view via the corresponding getter (if it exists);
     * otherwise, get the value directly from the '$_values' protected array
     */
    public function __get($name)
    {
        $accessor = 'get' . ucfirst(strtolower($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 view yet.");
    }

    /**
     * Check if the specified field has been assigned to the view
     */
    public function __isset($name)
    {
        return isset($this->_values[$name]);
    }

    /**
     * Unset the specified field from the view
     */
    public function __unset($name)
    {
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
            return true;
        }
        throw new \InvalidArgumentException("The field '$name' has not been set for this view yet.");
    }

    /**
     * Render the template
     */
    public function render()
    {
        extract($this->_values);
        ob_start();
        include $this->_templateFile;
        return ob_get_clean();
    }

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

Even though the definition of this view class is somewhat lengthy, its driving logic can be grasped quickly. Simply put, the class behaves like a data holder that assigns, removes and gets fields from spawned view objects, which are in turn rendered inside a specified template file. It's that simple. 

Since my purpose here is to show how to use the earlier proxy classes to lazy-load the comments corresponding to a particular blog entry (insertions, updates and deletions will be omitted for the sake of brevity), this is the template file that I’ll be using later on to accomplish this:

(MyApp/Views/Templates/entries.php)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Using the proxy pattern in PHP</title>
<style type="text/css">
body {
    padding: 0;
    margin: 0;
    background: #fff;
    font: 0.8em Arial, Helvetica, sans-serif;
    color: #585858;
}
h1 {
    font-size: 3em;
    color: #0080ff;
}
h2 {
    font-size: 1.8em;
    color: #E87400;
    margin: 0 0 10px 0;
}
h3 {
    font-size: 1.2em;
    color: #585858;
    margin: 0 0 15px 0;
}
p {
    margin: 0 0 15px 0;
    line-height: 18px;
}
#wrapper {
    width: 800px;
    margin: 0 auto;
}
.entry {
    padding: 20px;
    margin: 40px 0 20px 0;
    background: #f8f8f8;
    border: 1px solid #ccc;
}
.comment {
    padding: 20px;
    margin: 0 0 20px 80px;
    background: #eee;
    border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="wrapper">
    <h1><?php echo $header;?></h1>
    <?php foreach ($entries as $entry):?>
        <div class="entry">
            <h2><?php echo $entry->title;?></h2>
            <h3>Posted by <?php echo $entry->author;?></h3>
            <p><?php echo $entry->content;?></p>
        </div>
        <?php foreach ($entry->comments as $comment):?>
           <div class="comment">
                <h3><?php echo $comment->author;?> said:</h3>
                <p><?php echo $comment->content;?></p>
            </div>
        <?php endforeach;?>
    <?php endforeach;?>
</div>
</body>
</html>

Although the above template file is pretty trivial -- it only contains some CSS along with a few chunks of PHP code -- it shows how blog entries will be displayed on the browser, and most importantly, how the associated comments will be transparently pulled from the database on request.

Moreover, if you go back and check the implementation of the collection proxy class, you’ll recall that it fetches the pertinent comments when its “load()” method is called within a “foreach” loop (due to the implementation of the “IteratorAggregate” SPL interface). Well, that’s exactly what the template does!

With that said, are you starting to see a bit more clearly how all of the pieces that compose the blog’s structure will fit together? I'm sure you do. Nevertheless, there are still some additional components that need to be constructed first, to get a fully-functional version of the blog. These will be implemented in the last tutorial.      

Final Thoughts

In this penultimate chapter of the tutorial, I added to the existing structure of this sample blog program a host of useful components, including the classes that handle collections of entities and views, and most importantly, the ones responsible for lazy-loading domain objects from the database.

Despite of all these goodies, we haven't finished developing the program yet. It's necessary to encapsulate the interaction of mappers, collections and proxies behind a simpler and uncluttered API, which can easily be consumed by different front-ends. Not surprisingly, this additional API will be a service layer. In the final installment I’ll be showing you how to implement it in a few easy steps.

Don’t miss the last part!



 
 
>>> 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: