Virtual Proxies revisited
Posted by John Kleijn • Thursday, March 25. 2010 • Category: PHPOf all the well known design patterns related to ORM, the Proxy pattern (or more specifically, Virtual Proxy) is perhaps the most under-appreciated (with Unit of Work Controller and Value List Handler coming in second). This may be because it's not strictly a data source pattern, and you won't find in PoEAA chapter or Core J2EE Patterns. It is actually in the GoF, which doesn't contain any data source patterns. For me personally, Virtual Proxy will always be directly associated with data loading, as that is what I first used it for in 2006, although since then I have used for other occasions where object initialization was abnormally expensive. It is a pretty versatile pattern.
In a nutshell, a Virtual Proxy is a "lazy loading" pattern that defers initialization of an object until it needed. The proxy does not contain the actual resource, but "knows how to get it". It does this by extending a subjects class while delegating to an instance of that same class, the subject. Basically this is what a Decorator does. But instead of overriding methods to add behaviour to a decorated object, it overrides them to trigger initialization of the subject (loading from the database in the case of an ORM), before delegating to the subject. Like with a decorator you'll have to override every method so that it is delegated to the subject. This makes manually writing proxies a pain.
Soww, quicky test:
class GeneratorTestCase extends \PHPUnit_Framework_TestCase
{
public function testProxyIsSameType()
{
$proxyGenerator = new ProxyGenerator('kwd\blog\virtualproxy');
$code = $proxyGenerator->generate('SomeDomainType');
$this->assertNull(eval($code));
$this->assertType('kwd\blog\virtualproxy\SomeDomainType', new SomeDomainTypeProxy());
}
}
But I'm jumping ahead. To test that the proxy is indistinguishable from the subject we must:
- Assert the proxy has the same type as the domain class
- Assert that the proxy does not expose any other methods (i.e. define public or protected methods of its own)
- Assert that all public and private methods are overriden by the proxy
Eventually, this will lead to something like this testcase and this generator. These are actually end results so initially they were smaller.
But that's just for keeping up appearences. Now we must assert that the proxy has the correct behaviour. In other words, we must assert that all requests are delegated to the loaded subject. Because preferably we don't want to expose methods of our own, we can use reflection to set the loader object on the proxy. Or we could write a static entry point where the proxy would fetch the subject, but if we can avoid static access we should. We keep that in mind and move on. We can't concern ourselves with the behaviour of a "loader" too much yet. Before PHP 5.3 though, one would have to create a stub of the loader to move on at this stage, but thanks to the new ReflectionProperty::setAccesible(), we can bypass that for now and set the subject directly on the object.
/**
* Assert that all requests are delegated to the loaded subject
*/
public function testDelegatesToSubjectWhenUsingInterface()
{
$proxy = new SomeDomainTypeProxy();
$subjectRefl = new \ReflectionClass('kwd\blog\virtualproxy\SomeDomainType');
$proxyRefl = new \ReflectionClass('kwd\blog\virtualproxy\SomeDomainTypeProxy');
$subject = $this->getMock('kwd\blog\virtualproxy\SomeDomainType');
$property = $proxyRefl->getProperty('_subject');
$property->setAccessible(true);
$property->setValue($proxy, $subject);
$subject
->expects($this->once())
->method('doSomethingWithMoreState')
->will($this->returnValue('mockedValue'));
$this->assertSame('mockedValue', $proxy->doSomethingWithMoreState(new SomeDomainType()));
}
Now we have asserted that the proxy will delegate to the subject when present, now we have to make sure it is actually loaded. We have already determined we want to avoid static access. This means the proxy needs special initialization, and we can't change the interface, which would be needed to be able to pass the loader. In writing the previous test we encountered a solution: use reflection to set a loader object upon construction. We keep our hands off the proxy which works fine, and create a Factory. We should assert that proxies created by the Factory load the subject when the interface is used. At this stage we should stub a Loader class so we can mock this.
/**
* Subject loader for proxies
*
* @category virtualproxy
* @package kwd\blog\virtualproxy
*/
class Loader
{
public function load()
{
}
}
So we mock that and have it return a subject object. The resulting testcase looks like this:
/**
* Test case for the proxy factory
*
* @category virtualproxy
* @package kwd\blog\virtualproxy
*/
class FactoryTestCase extends \PHPUnit_Framework_TestCase
{
/**
* Assert that proxies load the subject when the interface is used
*/
public function testLoadsSubjectFromProxyWhenUsingInterface()
{
$factory = new ProxyFactory(new ProxyGenerator());
$loader = $this->getMock('kwd\blog\virtualproxy\Loader', array('load'));
$loader
->expects($this->once())
->method('load')
->will($this->returnValue(new domain\SomeOtherDomainType()));
$proxy = $factory->createProxy('kwd\blog\virtualproxy\domain\SomeOtherDomainType', $loader);
$this->assertType('kwd\blog\virtualproxy\domain\SomeOtherDomainType', $proxy);
$proxy->doSomethingWithState('foo');
}
}
Finished factory here.
The next step would be to build out the loaders, but that is out of the scope of this post.



ShareThis