Skip to content

Commit

Permalink
Adding Before and After annotations. Modified interface to IWrapper.
Browse files Browse the repository at this point in the history
  • Loading branch information
KrisJordan committed Jun 2, 2009
1 parent 3365b10 commit 19fafc5
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 6 deletions.
75 changes: 75 additions & 0 deletions recess/recess/lang/AfterAnnotation.class.php
@@ -0,0 +1,75 @@
<?php
Library::import('recess.lang.Annotation');
Library::import('recess.lang.MethodCallWrapper');

/**
* The After annotation can be used as a shortcut for registering
* methods that should be called after a wrapped method. It is placed
* on the method to be called and referencess the wrappable method.
*
* For example, on a subclass of Model:
* /** !After create, insert *_/
* function log($success) { echo $success ? "Success!" : "Fail!"; }
*
* There are a couple of nuances worth noting:
*
* -Your after method must take one argument that is the value returned
* the wrapped method.
* -Your method should return a value of the same type as expected
* to be returned by the wrapped method. Returning null, or not returning,
* has the same effect as returning the value returned by the wrapped
* method.
*
* @author Kris Jordan
* @since 0.20
*/
class AfterAnnotation extends Annotation {

/**
* Returns a string representation of the intended usage of an annotation.
*
* @return string
*/
public function usage() {
return '!After wrappableMethod[, wrappableMethod]*';
}

/**
* After is only used on methods.
*
* @return integer
*/
public function isFor() {
return Annotation::FOR_METHOD;
}

/**
* Validates some constraints of the annotation's specification.
*
* @param $class The classname the annotation is on.
*/
protected function validate($class) {
$this->minimumParameterCount(1);
$this->acceptsNoKeyedValues();
}

/**
* The expansion step of After creates a MethodCallWrapper
* for each wrappableMethod in the methods listed in the Annotation's
* parameters.
*
* @param string $class Classname the annotation is applied to.
* @param mixed $reflection The Reflection(Class|Method|Property) object the annotation is applied to.
* @param ClassDescriptor $descriptor The ClassDescriptor being manipulated.
*/
protected function expand($class, $reflection, $descriptor) {
$methodName = $reflection->getName();

foreach($this->values as $wrappableMethod) {
$wrapper = new MethodCallWrapper();
$wrapper->addCallAfter($methodName);
$descriptor->addWrapper($wrappableMethod, $wrapper);
}
}
}
?>
73 changes: 73 additions & 0 deletions recess/recess/lang/BeforeAnnotation.class.php
@@ -0,0 +1,73 @@
<?php
Library::import('recess.lang.Annotation');
Library::import('recess.lang.MethodCallWrapper');

/**
* The Before annotation can be used as a shortcut for registering
* methods that should be called before a wrapped method. It is placed
* on the method to be called and referencess the wrappable method.
*
* For example, on a subclass of Model:
* /** !Before create, insert *_/
* function validate() { echo "validate"; }
*
* There are a couple of nuances worth noting:
*
* -Your Before method must take the same arguments as the wrapped method.
* -If your method returns a non-null value it will short-circuit the call
* and immediately return your returned value without calling the wrapped
* method.
*
* @author Kris Jordan
* @since 0.20
*/
class BeforeAnnotation extends Annotation {

/**
* Returns a string representation of the intended usage of an annotation.
*
* @return string
*/
public function usage() {
return '!Before wrappableMethod[, wrappableMethod]*';
}

/**
* Before is only used on methods.
*
* @return integer
*/
public function isFor() {
return Annotation::FOR_METHOD;
}

/**
* Validates some constraints of the annotation's specification.
*
* @param $class The classname the annotation is on.
*/
protected function validate($class) {
$this->minimumParameterCount(1);
$this->acceptsNoKeyedValues();
}

/**
* The expansion step of Before creates a MethodCallWrapper
* for each wrappableMethod in the methods listed in the Annotation's
* parameters.
*
* @param string $class Classname the annotation is applied to.
* @param mixed $reflection The Reflection(Class|Method|Property) object the annotation is applied to.
* @param ClassDescriptor $descriptor The ClassDescriptor being manipulated.
*/
protected function expand($class, $reflection, $descriptor) {
$methodName = $reflection->getName();

foreach($this->values as $wrappableMethod) {
$wrapper = new MethodCallWrapper();
$wrapper->addCallBefore($methodName);
$descriptor->addWrapper($wrappableMethod, $wrapper);
}
}
}
?>
3 changes: 2 additions & 1 deletion recess/recess/lang/IWrapper.class.php
Expand Up @@ -27,10 +27,11 @@ function before($object, &$args);
* after is non-null then after's return value will override the return value
* of the wrapped method.
*
* @param $object
* @param $returnValue
* @return mixed Should be of the same type as the wrapped method returns.
*/
function after($returnValue);
function after($object, $returnValue);

/**
* Attempt to combine a wrapper with another. Example usage:
Expand Down
106 changes: 106 additions & 0 deletions recess/recess/lang/MethodCallWrapper.class.php
@@ -0,0 +1,106 @@
<?php
Library::import('recess.lang.IWrapper');

/**
* A Wrapper that will call methods on a class before or after calling the
* wrapped method. Used by Before and After annotations.
*
* @author Kris Jordan
* @since 0.20
*/
class MethodCallWrapper implements IWrapper {
protected $callBefore = array();
protected $callAfter = array();

/**
* Add methods to be called before the wrapped method.
* Methods must have same argument signature as the wrapped method.
* If a non-null value is returned from any method called
* before the wrapped method its return value will short circuit
* the call and return immediately without calling the method.
*/
public function addCallBefore() {
$args = func_get_args();
foreach($args as $method) {
if(is_string($method)){
$this->callBefore[] = $method;
}
}
}

/**
* Add methods to be called after the wrapped method has returned.
* Methods called after must take a single argument that is the
* return value of the called method. Methods called after are expected
* to return the value returned by the wrapped method unless it
* needs to be massaged in some way.
*/
public function addCallAfter() {
$args = func_get_args();
foreach($args as $method) {
if(is_string($method)){
$this->callAfter[] = $method;
}
}
}

/**
* Call all other methods on this class registered to be called before
* the wrapped method.
*
* @param $object
* @param $args
* @return mixed If a value is returned during a call to before that value short-circuits the method call.
*/
function before($object, &$args) {
$reflectedClass = new RecessReflectionClass($object);
foreach($this->callBefore as $method) {
$reflectedMethod = $reflectedClass->getMethod($method);
$result = $reflectedMethod->invokeArgs($object, $args);
if($result !== null) {
return $result;
}
}
// Return null so that method call proceeds as expected
return null;
}

/**
* Call all other methods on this class registered to be called after
* the wrapped method has returned.
*
* @param $object
* @param $returnValue
* @return mixed Should be of the same type as the wrapped method returns.
*/
function after($object, $returnValue) {
$reflectedClass = new RecessReflectionClass($object);
foreach($this->callAfter as $method) {
$reflectedMethod = $reflectedClass->getMethod($method);
$result = $reflectedMethod->invoke($object, $returnValue);
if($result !== null) {
$returnValue = $result;
}
}
return $returnValue;
}

/**
* Attempt to combine a wrapper with another. Example usage:
* Required wrappers declared on multiple fields of a model
* can be combined into a single wrapper.
*
* @param IWrapper $wrapper
* @return true on success, false on failure
*/
function combine(IWrapper $that) {
if($that instanceof MethodCallWrapper) {
$this->callBefore = array_merge($this->callBefore, $that->callBefore);
$this->callAfter = array_merge($this->callAfter, $that->callAfter);
return true;
} else {
return false;
}
}
}
?>
2 changes: 2 additions & 0 deletions recess/recess/lang/Object.class.php
Expand Up @@ -2,6 +2,8 @@
Library::import('recess.lang.ClassDescriptor');
Library::import('recess.lang.AttachedMethod');
Library::import('recess.lang.WrappableAnnotation');
Library::import('recess.lang.BeforeAnnotation');
Library::import('recess.lang.AfterAnnotation');

/**
* Object is the base class for extensible classes in the Recess.
Expand Down
2 changes: 1 addition & 1 deletion recess/recess/lang/WrappedMethod.class.php
Expand Up @@ -107,7 +107,7 @@ function call() {
$returns = $this->reflectedMethod->invokeArgs($object, $args);

foreach(array_reverse($this->wrappers) as $wrapper) {
$wrapperReturn = $wrapper->after($returns);
$wrapperReturn = $wrapper->after($object, $returns);
if($wrapperReturn != null) {
$returns = $wrapperReturn;
}
Expand Down
8 changes: 4 additions & 4 deletions recess/test/recess/lang/FrameworkObjectTest.php
Expand Up @@ -38,9 +38,9 @@ function shapeDescriptor($class, $reflection, $descriptor) {

function combine(IWrapper $requiredWrapper) {}

function before(&$args) {}
function before($object, &$args) {}

function after($result) { return $result + 1; }
function after($object, $result) { return $result + 1; }
}

class ConcatAnnotation extends Annotation implements IWrapper {
Expand All @@ -60,9 +60,9 @@ function shapeDescriptor($class, $reflection, $descriptor) {

function combine(IWrapper $requiredWrapper) {}

function before(&$args) { $args[1] = $this->prepend . $args[1]; }
function before($object, &$args) { $args[1] = $this->prepend . $args[1]; }

function after($result) { return $result . $this->append; }
function after($object, $result) { return $result . $this->append; }
}

/**
Expand Down

0 comments on commit 19fafc5

Please sign in to comment.