An Intro to Virtual Proxies, Part 2

Share this article

Standing behind a rather fancy and flowery name, Virtual Proxies are quite possibly one of the most visible examples of why the “Programming to Interfaces” mantra is a lot more than just a dull dogmatic principle. Resting on the foundation of Polymorphism (dynamic Polymorphism, not the ad-hoc one often achieved through plain method overriding), Virtual Proxies are a simple yet solid concept which allows you to defer the construction/loading of expensive object graphs without having to modify client code.

One of the great things about proxies is that they can be conceptually designed to work with either single objects or with collections of them (or with both, even though doing so can jeopardize separation of concerns and become difficult to manage over time). To demonstrate from a hands-on perspective how to exploit the functionality made available by Virtual Proxies, in the first instalment of this series I went through the development of a few examples, showing how to use a basic proxy for pulling in an aggregate from the database in order to fulfill a simplistic domain model.

Even though experience was hopefully didactic, and with luck also fun, its flip-side was somewhat deceiving, as it showed the nuts and bolts of Virtual Proxies but not how to implement them in a more realistic scenario. Proxies are a hard-to-beat armada when it comes to lazy-loading collections of domain objects from storage. To get an idea of the concept just think about a batch of blog posts where each set of related comments can be fetched on demand from the database; surely you’ll grasp why proxies can deliver superbly well what they promise in cases like this.

As usual, practice is the best teacher. In this part I’ll showcase how to hook up a proxy to a specific collection of domain objects. I’ll be recreating at a very basic level this typical scenario, that way you can see its driving logic in a pretty painless fashion.

Creating a Collection of Domain Objects

As I explained, Virtual Proxies are commonly brought to life when it comes to fetching aggregate roots from the persistence layer which are bound to a collection of underlying domain objects. Because of their inherently prolific nature, collections are, in many cases, expensive to setup up front, something that makes them good candidates for being dragged in on request to overhead caused by expensive trips to the database.

Moreover, considering the “one-to-many” association between blog posts and the corresponding comments reflects fairly faithfully in this use case, it’d be pretty instructive to first model the relationship through some straightforward domain classes before working on a concrete proxy.

To keep things easy to understand, the first two players I’ll be adding to the testing stage will be a segregated interface, along with a basic implementer. Joining forces, these two will define the contract and implementation of generic blog post objects:

<?php
namespace Model;
use ModelCollectionCommentCollectionInterface;

interface PostInterface
{
    public function setId($id);
    public function getId();

    public function setTitle($title);
    public function getTitle();

    public function setContent($content);
    public function getContent();

    public function setComments(CommentCollectionInterface $comments);
    public function getComments();
}
<?php
namespace Model;
    use ModelCollectionCommentCollectionInterface;

class Post implements PostInterface
{
    protected $id;
    protected $title;
    protected $content;
    protected $comments;

    public function __construct($title, $content, CommentCollectionInterface $comments = null)  {
        $this->setTitle($title);
        $this->setContent($content);
        $this->comments = $comments;
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this post has been set already.");
        }

        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The post ID is invalid.");
        }

        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setTitle($title) {
        if (!is_string($title) 
            || strlen($title) < 2 
            || strlen($title) > 100) {
            throw new InvalidArgumentException(
                "The post title is invalid.");
        }

        $this->title = htmlspecialchars(trim($title), ENT_QUOTES);
        return $this;
    }

    public function getTitle() {
        return $this->title;
    }

    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException(
                "The post content is invalid.");
        }

        $this->content = htmlspecialchars(trim($content), ENT_QUOTES);
        return $this;
    }

    public function getContent() {
        return $this->content;
    }

    public function setComments(CommentCollectionInterface $comments) {
        $this->comments = $comments;
        return $this;
    }

    public function getComments() {
        return $this->comments;
    }
}

Understanding the logic behind the above Post class is a trivial process that doesn’t need really require an explanation. Still, there’s a relevant detail worth noting here: the class explicitly declares in the constructor a dependency to a still undefined collection of comments. Let’s create now the class that spawns post comments:

<?php
namespace Model;

interface CommentInterface
{
     public function setId($id);
     public function getId();
     
     public function setContent($content);
     public function getContent();
     
     public function setPoster($poster);
     public function getPoster();
}
<?php
namespace Model;

class Comment implements CommentInterface
{
    protected $id;
    protected $content;
    protected $poster;
    
    public function __construct($content, $poster) {
        $this->setContent($content);
        $this->setPoster($poster);
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this comment has been set already.");
        }
        
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The comment ID is invalid.");
        }
        
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException(
                "The content of the comment is invalid.");
        }
        
        $this->content = htmlspecialchars(trim($content), ENT_QUOTES);
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setPoster($poster) {
        if (!is_string($poster) 
            || strlen($poster) < 2
            || strlen($poster) > 30) {
            throw new InvalidArgumentException(
                "The poster is invalid.");
        }
        
        $this->poster = htmlspecialchars(trim($poster), ENT_QUOTES);
        return $this;
    }
    
    public function getPoster() {
        return $this->poster;
    }
}

So far, things are rolling along smoothly. There’s not much that can be said about the above domain classes except that they’re slim blocks of a basic Domain Model, where each blog post object exposes a “one-to-many” association with the related comments. Feel free to call me a purist if you want, but in my view the model’s current implementation looks half-baked and clunky if it doesn’t get spiced up with a collection of comments. Let’s make the model a bit richer by adding to it the logic of this extra component:

<?php
namespace ModelCollection;

interface CommentCollectionInterface extends Countable, IteratorAggregate
{
    public function getComments();
}
<?php
namespace ModelCollection;
use ModelCommentInterface;

class CommentCollection implements CommentCollectionInterface
{
    protected $comments = array();

    public function __construct(array $comments = array()) {
        if ($comments) {
            foreach($comments as $comment) {
                $this->addComment($comment);
            } 
        }
   
    }
    
    public function addComment(CommentInterface $comment) {
        $this->comments[] = $comment;
        return $this;
    }
    
    public function getComments() {
        return $this->comments;
    }
    
    public function count() {
        return count($this->comments);
    }
    
    public function getIterator() {
        return new ArrayIterator($this->comments);
    }
}

If you’re observant and scan through the CommentCollection class, the first thing you’ll spot is the fact that it’s nothing but an iterable, countable array wrapper hidden behind a fancy disguise. In fact, array collections come in different forms and flavors, but most of the times they’re just plain usages of the Iterator and ArrayAccess SPL classes. In this case, I wanted to save myself (and you) from tackling such a boring task and made the class an implementer of IteratorAggregate.

With the comment collection already in place, we could just go one step further and put the domain model to do what it’s supposed to do – massage a few blog post objects here and there, and even interconnect them with a batch of comments fetched eagerly from the database. But in doing so we’d just be cheating ourselves and not exploiting the functionality that Virtual Proxies offer to its fullest extent.

Considering that in a typical implementation proxies expose the same API that the actual domain objects do, a proxy that interacts with the previous CommentCollection class should implement the CommentCollectionInterface as well to honor the contract with client code without dropping in somewhere a bunch of smelly conditionals.

Interfacing to Collections of Domain Objects via a Virtual Proxy

To be frank, array-wrapping collections like the one earlier can exist happily on their own without having to rely on any other dependencies. (If you’re skeptical, feel free to check how collections in Doctrine do their stuff behind the scenes.) Despite this, remember that I’m trying to implement a proxy that mimics the behavior of a real collection of post comments, but that is in fact a lightweight stand-in.

The question that begs asking is: how can the comments be pulled from the database and dropped into the earlier collection? There’s a few ways to accomplish this, but one that I find most appealing is through a data mapper since it promotes persistence agnosticism.

The mapper below does a decent job of fetching collections of post comments from storage. Check it out:

<?php
namespace ModelMapper;

interface CommentMapperInterface
{
    public function fetchById($id);
    
    public function fetchAll(array $conditions = array());
}
<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelCollectionCommentCollection,
    ModelComment;

class CommentMapper implements CommentMapperInterface
{  
    protected $adapter;
    protected $entityTable = "comments";
    
    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }
    
    public function fetchById($id) {
        $this->adapter->select($this->entityTable,
            array("id" => $id));
        
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        
        return $this->createComment($row);
    }
    
    public function fetchAll(array $conditions = array()) {
        $collection = new CommentCollection;
        $this->adapter->select($this->entityTable, $conditions);
        $rows = $this->adapter->fetchAll();
        
        if ($rows) {
            foreach ($rows as $row) {
                $collection->addComment($this->createComment($row));
            }
        }
     
        return $collection;
    }
    
    protected function createComment(array $row) {
        return new Comment($row["content"], $row["poster"]);
    }
}

While the finders exposed by the CommentMapper class in general stick to the API that one might expect in a standard data mapper implementation, by far the fetchAll() method is the flashiest kid on the block. It first pulls in all of the blog post comments from storage and drops them into the collection, which is finally returned to client code. If you’re like me, an alarm bell might be going off in your head because the collection is directly instantiated inside the method.

In fact, there’s no need to be frantic about new operators living shyly outside factories, at least in this case, as the collection is actually a generic structure that falls under an “newable” category rather than under an “injectable” one. Regardless, if you feel a little bit less guilty by injecting the collection in the mapper’s constructor, feel free to do so.

With the comment mapper in place, it’s time to go through the true epiphany moment and build the proxy class that mediates with the earlier collection:

<?php
namespace ModelProxy;
use ModelCollectionCommentCollectionInterface,
    ModelMapperCommentMapperInterface;

class CommentCollectionProxy implements CommentCollectionInterface
{
    protected $comments;
    protected $postId;
    protected $commentMapper;

    public function __construct($postId, CommentMapperInterface $commentMapper) {
        $this->postId = $postId;
        $this->commentMapper = $commentMapper;
    }
    
    public function getComments() {
        if ($this->comments === null) {
            
            if(!$this->comments = 
                $this->commentMapper->fetchAll(
                    array("post_id" => $this->postId))) {
                throw new UnexpectedValueException(
                    "Unable to fetch the comments.");
            }
        }
        
        return $this->comments;
    }
    
    public function count() {
        return count($this->getComments());
    }
    
    public function getIterator() {
        return $this->getComments();
    }
}

As you might expect, CommentCollectionProxy implements the same interface than one of the real comment collections. However, its getComments() method does the real leg work behind the scenes and lazy-loads the comments from the database through the mapper passed in the constructor.

This simple yet effective trick allows you to do all sort of clever things with the comments, without suffering from excessive underarm sweating. Do you want to see what ones? Well, let’s say you need to fetch all the comments bound to a specific blog post from the database. The following snippet gets the job done:

<?php	
use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperCommentMapper,
    ModelProxyCommentCollectionProxy,
    ModelComment,  
    ModelPost;
    
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=blog", "myfancyusername", "mysecretpassword");

$commentMapper = new CommentMapper($adapter);
$comments = $commentMapper->fetchAll(array("post_id" => 1));

$post = new Post("The post title", "This is just a sample post.",
    $comments);

echo $post->getTitle() . " " .  $post->getContent() . "<br />";

foreach ($post->getComments() as $comment) {
    echo $comment->getContent() . " " . $comment->getPoster() .
        "<br />";
}

The downside of this approach is that the comments are first pulled in from storage and then injected into the internals of the post object. How about doing the inverse, but this time by “fooling” client code with the proxy?

<?php
$comments = new CommentCollectionProxy(1, new CommentMapper($adapter)); 

$post = new Post("The post title", "This is just a sample post.",
    $comments);

echo $post->getTitle() . " " .  $post->getContent() . "<br />";

foreach ($post->getComments() as $comment) {
    echo $comment->getContent() . " " . $comment->getPoster() .
        "<br />";
}

Not only is the set of comments lazy-loaded transparently from the database after the proxy was dropped into the foreach loop, but the API exposed to the client code keeps its pristine structure untouched the whole time. Do we even dare ask for anything better? Unless you’re insanely greedy, I hardly think so.

In either case, at this point you should be aware of what’s actually going on under the hood of Virtual Proxies and how to make the most of the functionality when it comes to improving the efficiency of the operations traded between domain objects and the underlying persistence layer.

Closing Thoughts

Although trivial, specially if you’re bold enough to use it in production, the earlier example shows in a nutshell a few interesting concepts. First, Virtual Proxies are not only a snap to set up and use, but they’re hard to beat when it comes to mixing up different implementations at runtime in order to defer the execution of expensive tasks, such as lazy-loading large chunks of data from the storage layer, or creating heavyweight object graphs.

Second, they’re a classic example on how the use of Polymorphism can be an effective vaccine for reducing common Rigidity and Fragility issues that many object-oriented applications suffer from. Furthermore, as PHP is both easy in its object model and supporting Closures, it’s possible to cook up a nice mixture of these features and build proxies whose underlying logic is driven by the goodies of closures. If you want to tackle this challenge on your own, then you have my blessings in advance.

Image via imredesiuk / Shutterstock

Alejandro GervasioAlejandro Gervasio
View Author

Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.

Expert
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week