diff --git a/control/injector/AfterCallAspect.php b/control/injector/AfterCallAspect.php index ce7ab303d..3e663b510 100644 --- a/control/injector/AfterCallAspect.php +++ b/control/injector/AfterCallAspect.php @@ -22,6 +22,8 @@ interface AfterCallAspect { * The name of the method being called * @param string $args * The arguments that were passed to the method call + * @param mixed $result + * The result of calling the method on the real object */ - public function afterCall($proxied, $method, $args); + public function afterCall($proxied, $method, $args, $result); } diff --git a/control/injector/AopProxyService.php b/control/injector/AopProxyService.php index 65eb334c0..4520696d0 100644 --- a/control/injector/AopProxyService.php +++ b/control/injector/AopProxyService.php @@ -13,14 +13,35 @@ class AopProxyService { public $afterCall = array(); public $proxied; + + /** + * Because we don't know exactly how the proxied class is usually called, + * provide a default constructor + */ + public function __construct() { + + } public function __call($method, $args) { if (method_exists($this->proxied, $method)) { $continue = true; + $result = null; + if (isset($this->beforeCall[$method])) { - $result = $this->beforeCall[$method]->beforeCall($this->proxied, $method, $args); - if ($result === false) { - $continue = false; + $methods = $this->beforeCall[$method]; + if (!is_array($methods)) { + $methods = array($methods); + } + foreach ($methods as $handler) { + $alternateReturn = null; + $proceed = $handler->beforeCall($this->proxied, $method, $args, $alternateReturn); + if ($proceed === false) { + $continue = false; + // if something is set in, use it + if ($alternateReturn) { + $result = $alternateReturn; + } + } } } @@ -28,11 +49,20 @@ class AopProxyService { $result = call_user_func_array(array($this->proxied, $method), $args); if (isset($this->afterCall[$method])) { - $this->afterCall[$method]->afterCall($this->proxied, $method, $args, $result); + $methods = $this->afterCall[$method]; + if (!is_array($methods)) { + $methods = array($methods); + } + foreach ($methods as $handler) { + $return = $handler->afterCall($this->proxied, $method, $args, $result); + if (!is_null($return)) { + $result = $return; + } + } } - - return $result; } + + return $result; } } } diff --git a/control/injector/BeforeCallAspect.php b/control/injector/BeforeCallAspect.php index b2bca8f2b..4a53ce2f2 100644 --- a/control/injector/BeforeCallAspect.php +++ b/control/injector/BeforeCallAspect.php @@ -20,6 +20,9 @@ interface BeforeCallAspect { * The name of the method being called * @param string $args * The arguments that were passed to the method call + * @param mixed $alternateReturn + * An alternative return value that should be passed + * to the caller. Only has effect of beforeCall returns false */ - public function beforeCall($proxied, $method, $args); + public function beforeCall($proxied, $method, $args, &$alternateReturn); } diff --git a/tests/injector/AopProxyTest.php b/tests/injector/AopProxyTest.php new file mode 100644 index 000000000..57e62c407 --- /dev/null +++ b/tests/injector/AopProxyTest.php @@ -0,0 +1,107 @@ + + * @license BSD License http://www.silverstripe.org/bsd-license + */ +class AopProxyTest extends SapphireTest { + public function testBeforeMethodsCalled() { + $proxy = new AopProxyService(); + $aspect = new BeforeAfterCallTestAspect(); + $proxy->beforeCall = array( + 'myMethod' => $aspect + ); + + $proxy->proxied = new ProxyTestObject(); + + $result = $proxy->myMethod(); + + $this->assertEquals('myMethod', $aspect->called); + $this->assertEquals(42, $result); + } + + public function testBeforeMethodBlocks() { + $proxy = new AopProxyService(); + $aspect = new BeforeAfterCallTestAspect(); + $aspect->block = true; + + $proxy->beforeCall = array( + 'myMethod' => $aspect + ); + + $proxy->proxied = new ProxyTestObject(); + + $result = $proxy->myMethod(); + + $this->assertEquals('myMethod', $aspect->called); + + // the actual underlying method will NOT have been called + $this->assertNull($result); + + // set up an alternative return value + $aspect->alternateReturn = 84; + + $result = $proxy->myMethod(); + + $this->assertEquals('myMethod', $aspect->called); + + // the actual underlying method will NOT have been called, + // instead the alternative return value + $this->assertEquals(84, $result); + } + + public function testAfterCall() { + $proxy = new AopProxyService(); + $aspect = new BeforeAfterCallTestAspect(); + + $proxy->afterCall = array( + 'myMethod' => $aspect + ); + + $proxy->proxied = new ProxyTestObject(); + + $aspect->modifier = function ($value) { + return $value * 2; + }; + + $result = $proxy->myMethod(); + $this->assertEquals(84, $result); + } + +} + +class ProxyTestObject { + public function myMethod() { + return 42; + } +} + +class BeforeAfterCallTestAspect implements BeforeCallAspect, AfterCallAspect { + public $block = false; + + public $called; + + public $alternateReturn; + + public $modifier; + + public function beforeCall($proxied, $method, $args, &$alternateReturn) { + $this->called = $method; + + if ($this->block) { + if ($this->alternateReturn) { + $alternateReturn = $this->alternateReturn; + } + return false; + } + } + + public function afterCall($proxied, $method, $args, $result) { + if ($this->modifier) { + $modifier = $this->modifier; + return $modifier($result); + } + } +} \ No newline at end of file