API Updated aspect proxy service

- Updated AspectProxyService to handle multiple handlers for each proxied
  object's methods.
- Changed BeforeCallAspect to allow for providing a return value that
  should be returned to the caller instead of the proxied return value
- Changed AfterCallAspect behaviour to allow for returning the value of
  the aspect to the caller instead of the proxied return value
This commit is contained in:
Marcus Nyeholt 2013-12-05 12:21:31 +11:00
parent b8f4576647
commit b273f3b524
4 changed files with 150 additions and 8 deletions

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -0,0 +1,107 @@
<?php
/**
*
*
* @author <marcus@silverstripe.com.au>
* @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);
}
}
}