From 377cda68008e9482ac3c5f9765d4744d12c7afa1 Mon Sep 17 00:00:00 2001 From: Kris Jordan Date: Sun, 13 Sep 2009 01:51:33 -0400 Subject: [PATCH] Refactoring of Object's attachMethod to attach. Wrappable methods to be replaced by candy. --- recess/recess/lang/AttachedMethod.class.php | 42 +++++++++-- recess/recess/lang/ClassDescriptor.class.php | 51 ++++++-------- recess/recess/lang/Object.class.php | 70 ++++++++++--------- recess/test/recess/lang/ObjectTest.php | 26 +++++-- .../test/recess/lang/ReflectionClassTest.php | 2 +- .../test/recess/lang/ReflectionMethodTest.php | 2 +- 6 files changed, 117 insertions(+), 76 deletions(-) diff --git a/recess/recess/lang/AttachedMethod.class.php b/recess/recess/lang/AttachedMethod.class.php index d74a88c..50649c2 100644 --- a/recess/recess/lang/AttachedMethod.class.php +++ b/recess/recess/lang/AttachedMethod.class.php @@ -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 + * @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); @@ -33,6 +58,7 @@ private function getReflectionObject() { } } + /* Implementation of ReflectionMethod Members */ function isFinal() { return true; } function isAbstract() { return false; } function isPublic() { return true; } @@ -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; } } \ No newline at end of file diff --git a/recess/recess/lang/ClassDescriptor.class.php b/recess/recess/lang/ClassDescriptor.class.php index 126a30f..3c11bdb 100644 --- a/recess/recess/lang/ClassDescriptor.class.php +++ b/recess/recess/lang/ClassDescriptor.class.php @@ -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 + * @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. @@ -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. * @@ -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; - } - } } \ No newline at end of file diff --git a/recess/recess/lang/Object.class.php b/recess/recess/lang/Object.class.php index a71e371..419b554 100644 --- a/recess/recess/lang/Object.class.php +++ b/recess/recess/lang/Object.class.php @@ -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); } /** @@ -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 { @@ -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]); + } + } } \ No newline at end of file diff --git a/recess/test/recess/lang/ObjectTest.php b/recess/test/recess/lang/ObjectTest.php index cf63236..2d889a5 100644 --- a/recess/test/recess/lang/ObjectTest.php +++ b/recess/test/recess/lang/ObjectTest.php @@ -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 { @@ -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()); } @@ -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)); diff --git a/recess/test/recess/lang/ReflectionClassTest.php b/recess/test/recess/lang/ReflectionClassTest.php index c7c25aa..97a5195 100644 --- a/recess/test/recess/lang/ReflectionClassTest.php +++ b/recess/test/recess/lang/ReflectionClassTest.php @@ -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())); diff --git a/recess/test/recess/lang/ReflectionMethodTest.php b/recess/test/recess/lang/ReflectionMethodTest.php index db926dc..3dac685 100644 --- a/recess/test/recess/lang/ReflectionMethodTest.php +++ b/recess/test/recess/lang/ReflectionMethodTest.php @@ -6,7 +6,7 @@ use made\up\space\DummyAnnotation; class ReflectionMethodTest extends PHPUnit_Framework_TestCase { - + function testBasic() { DummyAnnotation::load(); $reflectionMethod = new ReflectionMethod('AClass','aMethod');