The Virtual Proxy Pattern

by Adam Wathan on May 13, 2014

When I was working on the relationship component of Faktory, I ran into a problem where telling a relationship to use a factory that wasn’t defined until later would blow everything up.

Faktory::define(['comment_with_post', 'Comment'], function($f) {
    $f->body = "Probably the greatest comment ever made.";
    $f->post = $f->belongsTo('post');
});

Faktory::define(['post', 'Post'], function($f) {
    $f->title = "My first post";
    $f->body = "Lorem ipsum dolor sit amet";
});

It would be annoying to have to define all of your factories in a carefully controlled order, so I needed to figure out a way to defer the loading of a factory until the relationship was actually being generated.

I didn’t want a relationship to know or care that I was lazy-loading the factory it needed, so I created a virtual proxy that could stand in without the relationship realizing it wasn’t carrying the actual factory instance.

Virtual Proxies

A Virtual Proxy is a lightweight object that stands in for another object, and knows how to find the real object once that object is actually needed.

When a relationship object is being instantiated in Faktory, it needs a reference to the factory it’s going to use to build the objects in that relationship:

// Factory.php
public function belongsTo($name, $foreign_key = null, $attributes = [])
{
    $factory = $this->factory_repository->getFactory($name);
    return new BelongsTo($this->model, $factory, $foreign_key, $attributes);
}

The trick here is that getFactory is actually returning a FactoryProxy, not a real factory object.

// Faktory.php
public function getFactory($name)
{
    return $this->getProxyFactory($name);
}

A FactoryProxy takes a closure in its constructor that when invoked, returns an instance of the actual factory requested:

// Faktory.php
protected function getProxyFactory($name)
{
    return new FactoryProxy(function() use ($name) {
        return $this->fetchFactory($name);
    });
}

// FactoryProxy.php
public function __construct($factory_loader)
{
    $this->factory_loader = $factory_loader;
}

As soon as any method is called on the FactoryProxy, it invokes the closure to retrieve the actual Factory instance, and then delegates all calls to real Factory:

// FactoryProxy.php
public function __call($method, $parameters)
{
    $instance = $this->getInstance();
    return call_user_func_array([$instance, $method], $parameters);
}

protected function getInstance()
{
    if (! isset($this->instance)) {
        $this->instance = $this->factory_loader->__invoke();
    }
    return $this->instance;
}

If it quacks like a duck…

The nice part about this approach is that the BelongsTo object has no idea that it’s getting a FactoryProxy instead of a Factory, and it really doesn’t care.

All that matters to the BelongsTo object is that it gets something that acts like a Factory. So as long as the object it receives can respond to the right method calls, it’s perfectly happy to use it.

Out of order

By using the virtual proxy pattern in Faktory, you can now define your factories in any order you want.

As long as the factory you want to use has been defined by the time you actually try to build an object, everything works out just fine, and the relationships themselves don’t have to even know that the proxies even exist.

So next time you find yourself in a situation where it would be convenient to lazy load something, give the virtual proxy pattern a look. It’s a great way to instantiate things just in time, while keeping that performance concern completely decoupled from it’s collaborators.

The following two tabs change content below.

Adam Wathan

Developer, powerlifter and resident Slayer fan.

Latest posts by Adam Wathan (see all)