Skip to content

Commit

Permalink
Refactoring of Object's attachMethod to attach. Wrappable methods to …
Browse files Browse the repository at this point in the history
…be replaced by candy.
  • Loading branch information
KrisJordan committed Sep 13, 2009
1 parent d4cb3a4 commit 377cda6
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 76 deletions.
42 changes: 37 additions & 5 deletions recess/recess/lang/AttachedMethod.class.php
Expand Up @@ -2,23 +2,48 @@
namespace recess\lang;

/**
* Data structure for an attached method. Holds a reference
* to an instance of an object and the mapped function on
* the object.
* The data structure behind dynamically attached methods in Recess.
* Since Recess 5.3 any callable can be attached at runtime. This class
* implements the same interface as a recess\lang\ReflectionMethod.
*
* @author Kris Jordan <krisjordan@gmail.com>
* @copyright 2008, 2009 Kris Jordan
* @package Recess PHP Framework
* @license MIT
* @link http://www.recessframework.org/
*/
class AttachedMethod {

/**
* The name that this method is attached as.
* @var string
*/
public $alias;

/**
* The callable that implements the functionality.
* @var callable
*/
public $callable;

/**
* Construct an AttachedMethod. Must be stored on a ClassDescriptor
* to have meaning/binding to a class.
*
* @param string $alias
* @param callable $callable
* @return AttachedMethod
*/
function __construct($alias, $callable) {
assert(is_callable($callable));
$this->callable = $callable;
$this->alias = $alias;
}

/**
* Returns a Reflection object for the attached callable.
*
* @return ReflectionMethod|ReflectionFunction
*/
private function getReflectionObject() {
if(is_string($this->callable)) {
return new \ReflectionFunction($this->callable);
Expand All @@ -33,6 +58,7 @@ private function getReflectionObject() {
}
}

/* Implementation of ReflectionMethod Members */
function isFinal() { return true; }
function isAbstract() { return false; }
function isPublic() { return true; }
Expand All @@ -50,11 +76,17 @@ function isUserDefined() { return true; }
function getFileName() { return $this->getReflectionObject()->getFileName(); }
function getStartLine() { return $this->getReflectionObject()->getStartLine(); }
function getEndLine() { return $this->getReflectionObject()->getEndLine(); }

/* Shifts the first parameter off because it is $self */
function getParameters() {
$params = $this->getReflectionObject()->getParameters();
array_shift($params);
return $params;
}

/* Returns one less than actually required of the implementor, because first is always $self */
function getNumberOfParameters() { return $this->getReflectionObject()->getNumberOfParameters() - 1; }
function getNumberOfRequiredParameters() { return $this->getReflectionObject()->getNumberOfRequiredParameters() - 1; }

/* Returns one less than actually required of the implementor, because first is always $self */
function getNumberOfRequiredParameters() { return $this->getReflectionObject()->getNumberOfRequiredParameters() - 1; }
}
51 changes: 21 additions & 30 deletions recess/recess/lang/ClassDescriptor.class.php
Expand Up @@ -2,16 +2,19 @@
namespace recess\lang;

/**
* Recess PHP Framework class info object that stores additional
* state about a Object. This additional state includes
* attached methods or named public properties.
* Recess Object class info data structure that stores
* meta-data and state for subclasses of Object. This additional
* state includes attached methods, wrapped methods, etc.
*
* @author Kris Jordan <krisjordan@gmail.com>
* @copyright 2008, 2009 Kris Jordan
* @package Recess PHP Framework
* @license MIT
* @link http://www.recessframework.org/
*/
class ClassDescriptor {

protected $attachedMethods = array();
protected $wrappedMethods = array();

/**
* Return a RecessAttachedMethod for given name, or return false.
Expand All @@ -26,6 +29,19 @@ function getAttachedMethod($methodName) {
return false;
}

/**
* Return a RecessAttachedMethod for given name, or return false.
*
* @param string $methodName Method name.
* @return RecessAttachedMethod on success, false on failure.
*/
function attached($methodName) {
if(isset($this->attachedMethods[$methodName]))
return $this->attachedMethods[$methodName]->callable;
else
return false;
}

/**
* Return all attached methods.
*
Expand Down Expand Up @@ -56,32 +72,7 @@ function addAttachedMethod($methodName, AttachedMethod $attachedMethod) {
function attachMethod($attachedMethodAlias, $callable) {
$attachedMethod = new AttachedMethod($attachedMethodAlias, $callable);
$this->addAttachedMethod($attachedMethodAlias, $attachedMethod);
return $callable;
}

/**
* Add a Wrapper to a WrappedMethod on this class descriptor.
*
* @param string $methodName
* @param IWrapper $wrapper
*/
function addWrapper($methodName, IWrapper $wrapper) {
if(!isset($this->wrappedMethods[$methodName])) {
$this->wrappedMethods[$methodName] = new WrappedMethod();
}
$this->wrappedMethods[$methodName]->addWrapper($wrapper);
}

/**
* Register a WrappedMethod on this class descriptor.
*
* @param string $methodName
* @param WrappedMethod $wrappedMethod
*/
function addWrappedMethod($methodName, WrappedMethod $wrappedMethod) {
if(isset($this->wrappedMethods[$methodName])) {
$this->wrappedMethods[$methodName] = $wrappedMethod->assume($this->wrappedMethods[$methodName]);
} else {
$this->wrappedMethods[$methodName] = $wrappedMethod;
}
}
}
70 changes: 37 additions & 33 deletions recess/recess/lang/Object.class.php
Expand Up @@ -57,27 +57,26 @@ abstract class Object {
protected static $descriptors = array();

/**
* Attach a method to a class. The result of this static method is the ability to
* call, on any instance of $attachOnClassName, a method named $attachedMethodAlias
* which delegates that method call to $providerInstance's $providerMethodName.
* Attach a callable to a class. The result of this static method is the ability to
* call a 'method' named $alias which delegates that method call to $callable where
* the first object is a reference to the object it was called on ($self).
*
* @param string $attachedMethodAlias
* @param callable $callable
* @param string $alias
* @param callable $callable: (Object,...) -> Any
* @return callable $callable Returns the callable for further chaining.
*/
static function attachMethod($attachedMethodAlias, $callable) {
static::getClassDescriptor()->attachMethod($attachedMethodAlias, $callable);
static function attach($alias, $callable) {
return static::getClassDescriptor()->attachMethod($alias, $callable);
}

/**
* Wrap a method on a class. The result of this static method is the provided IWrapper
* implementation will be called before and after the wrapped method.
* Returns an attached method with name $alias.
*
* @param string $wrapOnClassName
* @param string $wrappableMethodName
* @param IWrapper $wrapper
* @param $alias
* @return callable
*/
static function wrapMethod($wrapOnClassName, $wrappableMethodName, IWrapper $wrapper) {
static::getClassDescriptor($wrapOnClassName)->addWrapper($wrappableMethodName, $wrapper);
static function attached($alias) {
return static::getClassDescriptor()->attached($alias);
}

/**
Expand Down Expand Up @@ -105,36 +104,26 @@ final function __call($name, $arguments) {
* Return the ObjectInfo for provided Object instance.
*
* @param variant $classNameOrInstance - String Class Name or Instance of Recess Class
* @return ClassDescriptor
* @return recess\lang\ClassDescriptor
*/
final static function getClassDescriptor($classNameOrInstance = false) {
if($classNameOrInstance == false) {
$classNameOrInstance = get_called_class();
}

if($classNameOrInstance instanceof Object) {
$class = get_class($classNameOrInstance);
$instance = $classNameOrInstance;
} else {
if(is_string($classNameOrInstance)) {
$class = $classNameOrInstance;
if(class_exists($class, true)) {
$reflectionClass = new ReflectionClass($class);
if(!$reflectionClass->isAbstract()) {
$instance = new $class;
} else {
return new ClassDescriptor();
}
}
} else {
$class = get_class($classNameOrInstance);
}

if(!isset(self::$descriptors[$class])) {
if(!isset(self::$descriptors[$class])) {
// $cache_key = self::RECESS_CLASS_KEY_PREFIX . $class;
$descriptor = false; // Cache::get($cache_key);

if($descriptor === false) {
if($instance instanceof Object) {
$descriptor = call_user_func(array($class, 'buildClassDescriptor'));

if(is_subclass_of($class,'recess\lang\Object')) {
$descriptor = $class::buildClassDescriptor();
// Cache::set($cache_key, $descriptor);
self::$descriptors[$class] = $descriptor;
} else {
Expand Down Expand Up @@ -252,6 +241,21 @@ protected static function buildClassDescriptor() {
$descriptor = static::finalClassDescriptor($descriptor);

return $descriptor;
}
}

/**
* Clears a class' descriptor. So far there have only been
* two needs for this:
* 1. For testing purposes and
* 2. When some cached external dependency changes
* (i.e. after a database table has been created a model's
* descriptor should be cleared for the given run)
*/
public static function clearClassDescriptor() {
$class = get_called_class();
if(isset(self::$descriptors[$class])) {
unset(self::$descriptors[$class]);
}
}

}
26 changes: 20 additions & 6 deletions recess/test/recess/lang/ObjectTest.php
Expand Up @@ -7,26 +7,30 @@

class ObjectTest extends PHPUnit_Framework_TestCase {

function setUp() {
AnObject::clearClassDescriptor();
}

function testConstruct() {
$anObject = new AnObject();
$this->assertType('recess\lang\Object',$anObject);
}

function testAttachMethod() {
function testAttach() {
$provider = new IsTrueProvider();
AnObject::attachMethod('returnTrue',array($provider,'returnTrue'));
AnObject::attach('returnTrue',array($provider,'returnTrue'));
$anObject = new AnObject();
$this->assertTrue($anObject->returnTrue());
}

function testAttachPlainFunction() {
AnObject::attachMethod('returnTrue','returnTruePlain');
AnObject::attach('returnTrue','returnTruePlain');
$anObject = new AnObject();
$this->assertTrue($anObject->returnTrue());
}

function testRenamePlainFunction() {
AnObject::attachMethod('trueOrNotTrue','returnTruePlain');
AnObject::attach('trueOrNotTrue','returnTruePlain');
$anObject = new AnObject();
$this->assertTrue($anObject->trueOrNotTrue());
try {
Expand All @@ -38,7 +42,7 @@ function testRenamePlainFunction() {
}

function testAttachLambda() {
AnObject::attachMethod('returnTrue', function ($self) { return $self instanceof AnObject; });
AnObject::attach('returnTrue', function ($self) { return $self instanceof AnObject; });
$anObject = new AnObject();
$this->assertTrue($anObject->returnTrue());
}
Expand All @@ -47,13 +51,23 @@ function testGetAttachedMethods() {
$attachedMethods = AnObject::getAttachedMethods();
$this->assertEquals(0,count($attachedMethods));

AnObject::attachMethod('returnTrue', function ($self) { return $self instanceof AnObject; });
AnObject::attach('returnTrue', function ($self) { return $self instanceof AnObject; });
$anObject = new AnObject();
$attachedMethods = AnObject::getAttachedMethods();
$this->assertEquals(1, count($attachedMethods));
$this->assertType('recess\lang\AttachedMethod',$attachedMethods['returnTrue']);
}

function testClearIndividualDescriptor() {
$originalDescriptor = clone AnObject::getClassDescriptor();

AnObject::attach('returnTrue','returnTruePlain');
$this->assertNotEquals($originalDescriptor,AnObject::getClassDescriptor());

AnObject::clearClassDescriptor();
$this->assertEquals($originalDescriptor,AnObject::getClassDescriptor());
}

function testGetDescriptorAbstract() {
$attachedMethods = AbstractObject::getAttachedMethods();
$this->assertEquals(0,count($attachedMethods));
Expand Down
2 changes: 1 addition & 1 deletion recess/test/recess/lang/ReflectionClassTest.php
Expand Up @@ -32,7 +32,7 @@ function testGetMethods() {

function testGetAttachedMethods() {
DummyAnnotation::load();
ReflectionClassObject::attachMethod('aMethod',function($self){return true;});
ReflectionClassObject::attach('aMethod',function($self){return true;});
$childClass = new ReflectionClass('ReflectionClassObject');
$parentClass = new ReflectionClass('recess\lang\Object');
$this->assertEquals(2, count($childClass->getMethods())-count($parentClass->getMethods()));
Expand Down
2 changes: 1 addition & 1 deletion recess/test/recess/lang/ReflectionMethodTest.php
Expand Up @@ -6,7 +6,7 @@
use made\up\space\DummyAnnotation;

class ReflectionMethodTest extends PHPUnit_Framework_TestCase {

function testBasic() {
DummyAnnotation::load();
$reflectionMethod = new ReflectionMethod('AClass','aMethod');
Expand Down

0 comments on commit 377cda6

Please sign in to comment.