An Intro to Virtual Proxies, Part 1

Share this article

One of the most appealing facets of Polymorphism is that it is applicable in a wide variety of situations. Moreover, while the ability to switch out concrete implementations is in and of itself a fundamental object-oriented pillar, commonly used for creating loosely-coupled, interchangeable components, its usage falls shy when it comes to pulling on the reins of domain objects.

There’s a somewhat fragile rationale that stands behind this observation: in many cases, domain objects are conceptually modelled up front to operate on concrete sets of data and inside the boundaries of strict relationships which most likely won’t change over time, and where most of the varying business logic is placed in hierarchies of standalone strategy classes (here’s where actual Polymorphism takes place). In such cases, certainly there’s not much room or even compelling reasons to justify having different implementations of domain objects at runtime except for mocking/testing.

Furthermore, very few will disagree that programming to interfaces is a bad thing, but isn’t it overkill to have one per domain object? After all, a user object will always be modelled with a few typical roles in mind, and if its login() method ever needs to be updated, well… it’ll just be refactored accordingly and the client code won’t complain so long as the mutual contract is maintained. At a glance, it seems that having a whole new user implementation not only isn’t very pragmatic, but it’s simply absurd.

A common pitfall with this approach is made apparent when it’s necessary to pull in an aggregate (a domain object made up of other objects or collections) from the database. As each reference to the aggregate implies dealing face to face with the real domain objects, the process unavoidably ends up dropping the entire object graph into the client. It’s not exactly a solution to praise with fervency, even if the objects can be cached later on.

As usual, simple solutions are the most effective ones, and this applies to the above problem. Rather than fetching the real fat aggregate, if a lightweight substitute is used instead which shares the same interface and knows how to get the aggregate in question from storage then lazy-loading the underlying objects becomes a straightforward process. Often referenced by a few other fancy names, the substitute is generically called a virtual proxy, a sort of stand-in that exploits the neatness of Polymorphism and interacts with the actual domain objects.

Proxies aren’t new to PHP. Doctrine and Zend Framework 2.x make use of them, although with different aims. On behalf of a didactic cause, however, it would be pretty instructive to implement some custom proxy classes and use them for lazy-loading a few basic aggregates from the database, this way illustrating how virtual proxies do their stuff under the hood.

Considering the functionality of proxies can be easily accommodated to coordinate either with single domain objects or with collections of them (or both), in the first part of this two-part series I’ll be showcasing the former user case, while in the second installment I’ll dig deeper into the complexities of the latter.

So, let’s now move along and get things finally rolling with virtual proxies.

Setting Up a Domain Model

As I pointed out in the introduction, proxies are simple -yet powerful- structures that allow you to pull in requested domain objects from underlying storage. But as one might expect, it would first be warranted to create a simple Domain Model so at least one of its building objects can be interfaced to a proxy.

The fist domain class I’ll add to the sample model will be one that represents blog posts. The class, along with its segregated interface, look like this:

<?php
namespace Model;

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 setAuthor(AuthorInterface $author);
    public function getAuthor();
}
<?php
namespace Model;

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

    public function __construct($title, $content, AuthorInterface $author) {
        $this->setTitle($title);
        $this->setContent($content);
        $this->setAuthor($author);
    }

    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 setAuthor(AuthorInterface $author) {
        $this->author = $author;
        return $this;
    }

    public function getAuthor() {
        return $this->author;
    }
}

The behavior of the Post class is trivial as it just implements a few mutators/accessors with some basic filtering/validation. Notice, however, that the class injects an author’s implementation in the constructor, that way setting a one-to-one relationship with the corresponding author.

To get the domain model up and running, the author in question must be modeled as well. Here’s the related class, along with its contract:

<?php
namespace Model;

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

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}
<?php
namespace Model;

class Author implements AuthorInterface
{
    protected $id;
    protected $name;
    protected $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

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

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

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

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

    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The name of the author is invalid.");
        }

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

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The email of the author is invalid.");
        }

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

    public function getEmail() {
        return $this->email;
    }
}

The same that I just said about the Post class applies to its collaborator, Author. There subtle detail worth stressing here is this: if the domain model were put to roll down the road right now, then any post object pulled in from the database would get an author object eagerly attached to it, something that fully honors its condition of aggregate.

While this is all well and fine since authors aren’t expensive to set per se, there might be times when they could be loaded only on request. If this happens, all the last-minute retrieval logic should be placed outside the domain objects’ confines, but at the same time controlled by an object that understands the model.

There’s no need to sink in the waters of anguish here, as this apparently-complex condition has a dead simple solution: if an author proxy is used instead of a real author, then it’s feasible to lazy-load the latter without spoiling the contract with client code since the proxy exposes the same API. This itself proves Polymorphism is hard to knock when it comes to swapping out domain object implementations in order to optimize your database persistence/retrieval strategy.

Curiosity is a pretty itching bug indeed, so let’s satisfy our own one and see how to create the above mentioned author proxy.

Loading Authors on Request via Virtual Proxies

Considering the proxy should be able to fetch author objects from the database, a neat way to do so would be through the API of a data mapper. In this particular case, the one shown below will get the job done nicely:

<?php namespace ModelMapper;

interface AuthorMapperInterface
{
    public function fetchById($id);
}
<?php
namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelAuthor;

class AuthorMapper implements AuthorMapperInterface
{    
    protected $entityTable = "authors";
    
    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 new Author($row["name"], $row["email"]);
    }
}

So far, so good. With the user mapper already in place, it’s time to tackle the creation of the author proxy. As stated before, it shares the same interface with the one implemented by real authors, and its core implementation is as follows:

<?php
namespace ModelProxy;
use ModelMapperAuthorMapperInterface,
    ModelAuthorInterface;

class AuthorProxy implements AuthorInterface
{
    protected $author;
    protected $authorId;
    protected $authorMapper;

    public function __construct($authorId, AuthorMapperInterface $authorMapper) {
        $this->authorId = $authorId;
        $this->authorMapper = $authorMapper;
    }
    
    public function setId($id) {
        $this->authorId = $id;
        return $this;
    }
    
    public function getId() {
        return $this->authorId;
    }

    public function setName($name) {
        $this->loadAuthor();
        $this->author->setName($name);
        return $this;
    }
    
    public function getName() {
        $this->loadAuthor();
        return $this->author->getName();
    }
    
    public function setEmail($email) {
        $this->loadAuthor();
        $this->author->setEmail($email);
        return $this;
    }
    
    public function getEmail() {
        $this->loadAuthor();
        return $this->author->getEmail();
    }
    
    protected function loadAuthor() {
        if ($this->author === null) {
            
            if(!$this->author = $this->authorMapper->fetchById($this->authorId)) {
                throw new UnexpectedValueException(
                    "Unable to fetch the author.");
            }
        }
        
        return $this->author;
    }
}

At a quick glance the AuthorProxy class looks like a cheap and dirty duplicate of Author with little or no extra functionality, but I assure you this is nothing but a shallow impression. When analyzed it shows the logic that stands behind a virtual proxy in a nutshell. The loadAuthor() method is the proxy’s actual workhorse, as its responsibility is to fetch from the database once, and only once, an author object is injected into the constructor. The batch of additional methods are just wrappers for the author’ setters/getters.

All in all it should be clear that constructing a proxy class that transparently handles a few domain objects behind the scenes is a lot more approachable than one might think. Even so, the best way for catching up the proxy’s real strength is by example. Let’s suppose we need to build an application that fetches a few popular quotes from a database and then dumps them to screen along with their corresponding author.

In a simple implementation, the application would look pretty much like this:

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

$adapter = new PdoAdapter("mysql:dbname=mydatabase", "dbuser", "dbpassword");

$authorMapper = new AuthorMapper($adapter);

$author = $authorMapper->fetchById(1);

$post = new Post(
    "About Men",
    "Men are born ignorant, not stupid; they are made stupid by education.",
    $author);

echo $post->getTitle() . $post->getContent() . " Quote from: " .
    $post->getAuthor()->getName();

Even when the quotes’ mapper has been excluded for the sake of brevity, the code’s flow is still pretty easy to understand: it pulls in the first author object (a reference to Bertrand Russell) from the database which gets injected into the quote object. Here the author has been eagerly fetched from the storage before having any chances to process it, which isn’t a cardinal sin by the way.

The purist madman living inside our heads can’t just be tossed aside so easily, though, and keeps murmuring how the author would be better off lazy-loaded. In such a case, we could switch over the proxy instead, and drop it into the application, as follows:

<?php
$author = new AuthorProxy(1, new AuthorMapper($adapter)); 

$post = new Post(
    "About Men",
    "Men are born ignorant, not stupid; they are made stupid by education.",
    $author);

echo $post->getTitle() . $post->getContent() . " Quote from: " .
    $post->getAuthor()->getName();

If you take a close look at the line responsible for creating the post object, you’ll notice that it remains exactly the same as the one from before even though it now takes the proxy and lazy-loads the author from the database. Asides from illustrating how to make use of virtual proxies in a common use case, the example here shows how to keep things copesetic with client code as well. It sticks to the Open/Closed principle and relies entirely on a few segregated interfaces rather than on concrete implementations.

To sum things up: if you’re still wondering if Polymorphism has a place when it comes to creating domain objects that can be swapped up at runtime by virtual proxies (or something along that line), then rest assured it can help you build up scalable, future-proof domain models.

Closing Thoughts

Virtual proxies come in a wide variety of flavors and forms, hence there’s no shortage of options when it comes to exploiting their functionality right out of the box. In many cases, they’re used as placeholders for actual domain objects that are rather expensive to create ahead in time, which makes it possible to load them transparently on request from the persistence layer without poisoning your client code with harmful conditionals.

In the earlier example, a basic proxy was utilized for fetching a simple aggregate from a database, something hopefully instructive in the end, but not entirely in line with the real world. In more realistic situations, domain models are usually much fatter than that and are generally plagued by itteratable collections of domain objects.

Not surprisingly, virtual proxies can be implemented for interplaying with collections as well without much fuss. So, in the next part I’ll show you how to create a collection-targeted proxy class so you can see for yourself if it fits your needs.

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