From 0377798b8aae34c9377d5519cdddc27c6746b46f Mon Sep 17 00:00:00 2001 From: Kris Jordan Date: Sat, 22 Aug 2009 20:36:24 -0400 Subject: [PATCH] Bringing in code, pre-quarantine. --- recess/recess/bootstrap.php | 8 + recess/recess/cache/Cache.class.php | 680 +++++++++--------- recess/recess/lang/AfterAnnotation.class.php | 147 ++-- recess/recess/lang/Annotation.class.php | 600 ++++++++-------- recess/recess/lang/AttachedMethod.class.php | 95 +-- recess/recess/lang/BeforeAnnotation.class.php | 143 ++-- recess/recess/lang/ClassDescriptor.class.php | 179 +++-- recess/recess/lang/ErrorException.class.php | 11 + recess/recess/lang/Exception.class.php | 15 + recess/recess/lang/IWrapper.class.php | 94 +-- .../recess/lang/MethodCallWrapper.class.php | 210 +++--- recess/recess/lang/Object.class.php | 529 +++++++------- recess/recess/lang/ReflectionClass.class.php | 65 ++ recess/recess/lang/ReflectionMethod.class.php | 35 + .../recess/lang/ReflectionProperty.class.php | 33 + .../recess/lang/WrappableAnnotation.class.php | 123 ++-- recess/recess/lang/WrappedMethod.class.php | 235 +++--- .../InvalidAnnotationValueException.class.php | 21 +- .../UnknownAnnotationException.class.php | 20 +- 19 files changed, 1710 insertions(+), 1533 deletions(-) create mode 100644 recess/recess/bootstrap.php create mode 100644 recess/recess/lang/ErrorException.class.php create mode 100644 recess/recess/lang/Exception.class.php create mode 100644 recess/recess/lang/ReflectionClass.class.php create mode 100644 recess/recess/lang/ReflectionMethod.class.php create mode 100644 recess/recess/lang/ReflectionProperty.class.php diff --git a/recess/recess/bootstrap.php b/recess/recess/bootstrap.php new file mode 100644 index 0000000..e2ee13d --- /dev/null +++ b/recess/recess/bootstrap.php @@ -0,0 +1,8 @@ + - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - **/ -abstract class Cache { - protected static $reportsTo; - - protected static $inMemory = array(); - - /** - * Push a cache provider onto the providers stack. Most expensive providers - * should be pushed first. - * - * @param ICacheProvider $cache - */ - static function reportsTo(ICacheProvider $cache) { - if(!$cache instanceof ICacheProvider) { - $cache = new NoOpCacheProvider(); - } - - if(isset(self::$reportsTo)) { - $temp = self::$reportsTo; - self::$reportsTo = $cache; - self::$reportsTo->reportsTo($temp); - } else { - self::$reportsTo = $cache; - } - } - - /** - * Set a cache value. - * - * @param string $key - * @param mixed $value - * @param int $duration - */ - static function set($key, $value, $duration = 0) { - self::$inMemory[$key] = $value; - self::$reportsTo->set($key, $value, $duration); - } - - /** - * Fetch a value from cache. - * - * @param string $key - * @return mixed - */ - static function get($key) { - if(isset(self::$inMemory[$key])) { - return self::$inMemory[$key]; - } else { - return self::$reportsTo->get($key); - } - } - - /** - * Remove a key value pair from cache. - * - * @param string $key - */ - static function delete($key) { - unset(self::$inMemory[$key]); - self::$reportsTo->delete($key); - } - - /** - * Clear all values from the cache. - */ - static function clear() { - self::$inMemory = array(); - self::$reportsTo->clear(); - } -} - -/** - * Common interface for caching subsystems. - * @author Kris Jordan - */ -interface ICacheProvider { - /** - * Enter description here... - * - * @param string $key - * @param mixed $value - * @param unknown_type $duration - */ - function set($key, $value, $duration = 0); - function get($key); - function delete($key); - function clear(); -} - -class NoOpCacheProvider implements ICacheProvider { - function set($key, $value, $duration = 0) {} - function get($key) { return false; } - function delete($key) {} - function clear() {} -} - -Cache::reportsTo(new NoOpCacheProvider()); - -/** - * Alternative PHP Cache provider. - * @see http://us3.php.net/apc - */ -class ApcCacheProvider implements ICacheProvider { - protected $reportsTo; - - function reportsTo(ICacheProvider $cache) { - if(!$cache instanceof ICacheProvider) { - $cache = new NoOpCacheProvider(); - } - - if(isset($this->reportsTo)) { - $temp = $this->reportsTo; - $this->reportsTo = $cache; - $this->reportsTo->reportsTo($temp); - } else { - $this->reportsTo = $cache; - } - } - - function set($key, $value, $duration = 0) { - apc_store($key, $value, $duration); - $this->reportsTo->set($key, $value, $duration); - } - - function get($key) { - $result = apc_fetch($key); - if($result === false) { - $result = $this->reportsTo->get($key); - if($result !== false) { - $this->set($key, $result); - } - } - return $result; - } - - function delete($key) { - apc_delete($key); - $this->reportsTo->delete($key); - } - - function clear() { - apc_clear_cache('user'); - $this->reportsTo->clear(); - } -} - -/** - * Memcache Provider - * @see http://us2.php.net/memcache - */ -class MemcacheCacheProvider implements ICacheProvider { - protected $reportsTo; - protected $memcache; - - function __construct($host = 'localhost', $port = 11211) { - $this->memcache = new Memcache; - $this->memcache->pconnect($host, $port); - } - - function reportsTo(ICacheProvider $cache) { - if(!$cache instanceof ICacheProvider) { - $cache = new NoOpCacheProvider(); - } - - if(isset($this->reportsTo)) { - $temp = $this->reportsTo; - $this->reportsTo = $cache; - $this->reportsTo->reportsTo($temp); - } else { - $this->reportsTo = $cache; - } - } - - function set($key, $value, $duration = 0) { - $this->memcache->set($key, $value, null, $duration); - $this->reportsTo->set($key, $value, $duration); - } - - function get($key) { - $result = $this->memcache->get($key); - if($result === false) { - $result = $this->reportsTo->get($key); - if($result !== false) { - $this->set($key, $result); - } - } - return $result; - } - - function delete($key) { - $this->memcache->delete($key); - $this->reportsTo->delete($key); - } - - function clear() { - $this->memcache->flush(); - $this->reportsTo->clear(); - } -} - -/** - * Provider implemented with Sqlite backend. Less preferable than - * APC/Memcache but works well for shared hosts. - */ -class SqliteCacheProvider implements ICacheProvider { - protected $reportsTo; - protected $pdo; - protected $setStatement; - protected $getStatement; - protected $getManyStatement; - protected $deleteStatement; - protected $time; - - protected $entries = array(); - - const VALUE = 0; - const EXPIRE = 1; - const KEY = 2; - - function __construct() { - $this->pdo = new Pdo('sqlite:' . $_ENV['dir.temp'] . 'sqlite-cache.db'); - $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - $tries = 0; - while($tries < 2) { - try { - $this->setStatement = $this->pdo->prepare('INSERT OR REPLACE INTO cache (key,value,expire) values (:key,:value,:expire)'); - $this->getStatement = $this->pdo->prepare('SELECT value,expire FROM cache WHERE key = :key'); - $this->getManyStatement = $this->pdo->prepare('SELECT value,expire,key FROM cache WHERE key LIKE :key'); - break; - } catch(PDOException $e) { - - try { - $this->pdo->exec('CREATE TABLE "cache" ("key" TEXT PRIMARY KEY NOT NULL , "value" TEXT NOT NULL , "expire" INTEGER NOT NULL)'); - $this->pdo->exec('CREATE INDEX "expiration" ON "cache" ("expire" ASC)'); - } catch(PDOException $e) { - if($tries == 1) { - die('Could not create cache table'); - } - sleep(1); - $tries++; - continue; - } - } - } - - $this->time = time(); - } - - function reportsTo(ICacheProvider $cache) { - if(!$cache instanceof ICacheProvider) { - $cache = new NoOpCacheProvider(); - } - - if(isset($this->reportsTo)) { - $temp = $this->reportsTo; - $this->reportsTo = $cache; - $this->reportsTo->reportsTo($temp); - } else { - $this->reportsTo = $cache; - } - } - - function set($key, $value, $duration = 0) { - $this->setStatement->execute(array(':key' => $key, ':value' => serialize($value), ':expire' => $duration == 0 ? 0 : time() + $duration)); - $this->reportsTo->set($key, $value, $duration); - $this->entries[$key] = $value; - } - - function clearStaleEntries() { - $this->pdo->exec('DELETE FROM cache WHERE expire != 0 AND expire < ' . $this->time); - } - - function get($key) { - if(isset($this->entries[$key])) { - return $this->entries[$key]; - } - - if(($starPos = strpos($key,'*')) === false) { - // Fetch Single - $this->getStatement->execute(array(':key' => $key)); - $entries = $this->getStatement->fetchAll(PDO::FETCH_NUM); - } else { - // Prefetch With Wildcard - $this->getManyStatement->execute(array(':key' => substr($key,0,$starPos+1) . '%')); - $entries = $this->getManyStatement->fetchAll(PDO::FETCH_NUM); - } - - $clearStaleEntries = false; - foreach($entries as $entry) { - if($entry[self::EXPIRE] == 0 || $entry[self::EXPIRE] <= $this->time) { - if(isset($entry[self::KEY])) { - $this->entries[$entry[self::KEY]] = unserialize($entry[self::VALUE]); - } else { - $this->entries[$key] = unserialize($entry[self::VALUE]); - } - } else { - $clearStaleEntries = true; - } - } - - if($clearStaleEntries) { - $this->clearStaleEntries(); - } - - if(isset($this->entries[$key])) { - return $this->entries[$key]; - } else{ - return $this->reportsTo->get($key); - } - } - - function delete($key) { - if($this->deleteStatement == null) { - $this->deleteStatement = $this->pdo->prepare('DELETE FROM cache WHERE key = :key OR (expire != 0 AND expire < ' . $this->time . ')'); - } - $this->deleteStatement->execute(array(':key' => $key)); - $this->reportsTo->delete($key); - } - - function clear() { - $this->pdo->exec('DELETE FROM cache'); - $this->reportsTo->clear(); - } -} -?> + + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + **/ +abstract class Cache { + protected static $reportsTo; + + protected static $inMemory = array(); + + /** + * Push a cache provider onto the providers stack. Most expensive providers + * should be pushed first. + * + * @param ICacheProvider $cache + */ + static function reportsTo(ICacheProvider $cache) { + if(!$cache instanceof ICacheProvider) { + $cache = new NoOpCacheProvider(); + } + + if(isset(self::$reportsTo)) { + $temp = self::$reportsTo; + self::$reportsTo = $cache; + self::$reportsTo->reportsTo($temp); + } else { + self::$reportsTo = $cache; + } + } + + /** + * Set a cache value. + * + * @param string $key + * @param mixed $value + * @param int $duration + */ + static function set($key, $value, $duration = 0) { + self::$inMemory[$key] = $value; + self::$reportsTo->set($key, $value, $duration); + } + + /** + * Fetch a value from cache. + * + * @param string $key + * @return mixed + */ + static function get($key) { + if(isset(self::$inMemory[$key])) { + return self::$inMemory[$key]; + } else { + return self::$reportsTo->get($key); + } + } + + /** + * Remove a key value pair from cache. + * + * @param string $key + */ + static function delete($key) { + unset(self::$inMemory[$key]); + self::$reportsTo->delete($key); + } + + /** + * Clear all values from the cache. + */ + static function clear() { + self::$inMemory = array(); + self::$reportsTo->clear(); + } +} + +/** + * Common interface for caching subsystems. + * @author Kris Jordan + */ +interface ICacheProvider { + /** + * Enter description here... + * + * @param string $key + * @param mixed $value + * @param unknown_type $duration + */ + function set($key, $value, $duration = 0); + function get($key); + function delete($key); + function clear(); +} + +class NoOpCacheProvider implements ICacheProvider { + function set($key, $value, $duration = 0) {} + function get($key) { return false; } + function delete($key) {} + function clear() {} +} + +Cache::reportsTo(new NoOpCacheProvider()); + +/** + * Alternative PHP Cache provider. + * @see http://us3.php.net/apc + */ +class ApcCacheProvider implements ICacheProvider { + protected $reportsTo; + + function reportsTo(ICacheProvider $cache) { + if(!$cache instanceof ICacheProvider) { + $cache = new NoOpCacheProvider(); + } + + if(isset($this->reportsTo)) { + $temp = $this->reportsTo; + $this->reportsTo = $cache; + $this->reportsTo->reportsTo($temp); + } else { + $this->reportsTo = $cache; + } + } + + function set($key, $value, $duration = 0) { + apc_store($key, $value, $duration); + $this->reportsTo->set($key, $value, $duration); + } + + function get($key) { + $result = apc_fetch($key); + if($result === false) { + $result = $this->reportsTo->get($key); + if($result !== false) { + $this->set($key, $result); + } + } + return $result; + } + + function delete($key) { + apc_delete($key); + $this->reportsTo->delete($key); + } + + function clear() { + apc_clear_cache('user'); + $this->reportsTo->clear(); + } +} + +/** + * Memcache Provider + * @see http://us2.php.net/memcache + */ +class MemcacheCacheProvider implements ICacheProvider { + protected $reportsTo; + protected $memcache; + + function __construct($host = 'localhost', $port = 11211) { + $this->memcache = new \Memcache; + $this->memcache->pconnect($host, $port); + } + + function reportsTo(ICacheProvider $cache) { + if(!$cache instanceof ICacheProvider) { + $cache = new NoOpCacheProvider(); + } + + if(isset($this->reportsTo)) { + $temp = $this->reportsTo; + $this->reportsTo = $cache; + $this->reportsTo->reportsTo($temp); + } else { + $this->reportsTo = $cache; + } + } + + function set($key, $value, $duration = 0) { + $this->memcache->set($key, $value, null, $duration); + $this->reportsTo->set($key, $value, $duration); + } + + function get($key) { + $result = $this->memcache->get($key); + if($result === false) { + $result = $this->reportsTo->get($key); + if($result !== false) { + $this->set($key, $result); + } + } + return $result; + } + + function delete($key) { + $this->memcache->delete($key); + $this->reportsTo->delete($key); + } + + function clear() { + $this->memcache->flush(); + $this->reportsTo->clear(); + } +} + +/** + * Provider implemented with Sqlite backend. Less preferable than + * APC/Memcache but works well for shared hosts. + */ +class SqliteCacheProvider implements ICacheProvider { + protected $reportsTo; + protected $pdo; + protected $setStatement; + protected $getStatement; + protected $getManyStatement; + protected $deleteStatement; + protected $time; + + protected $entries = array(); + + const VALUE = 0; + const EXPIRE = 1; + const KEY = 2; + + function __construct() { + $this->pdo = new \Pdo('sqlite:' . $_ENV['dir.temp'] . 'sqlite-cache.db'); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $tries = 0; + while($tries < 2) { + try { + $this->setStatement = $this->pdo->prepare('INSERT OR REPLACE INTO cache (key,value,expire) values (:key,:value,:expire)'); + $this->getStatement = $this->pdo->prepare('SELECT value,expire FROM cache WHERE key = :key'); + $this->getManyStatement = $this->pdo->prepare('SELECT value,expire,key FROM cache WHERE key LIKE :key'); + break; + } catch(\PDOException $e) { + + try { + $this->pdo->exec('CREATE TABLE "cache" ("key" TEXT PRIMARY KEY NOT NULL , "value" TEXT NOT NULL , "expire" INTEGER NOT NULL)'); + $this->pdo->exec('CREATE INDEX "expiration" ON "cache" ("expire" ASC)'); + } catch(PDOException $e) { + if($tries == 1) { + die('Could not create cache table'); + } + sleep(1); + $tries++; + continue; + } + } + } + + $this->time = time(); + } + + function reportsTo(ICacheProvider $cache) { + if(!$cache instanceof ICacheProvider) { + $cache = new NoOpCacheProvider(); + } + + if(isset($this->reportsTo)) { + $temp = $this->reportsTo; + $this->reportsTo = $cache; + $this->reportsTo->reportsTo($temp); + } else { + $this->reportsTo = $cache; + } + } + + function set($key, $value, $duration = 0) { + $this->setStatement->execute(array(':key' => $key, ':value' => serialize($value), ':expire' => $duration == 0 ? 0 : time() + $duration)); + $this->reportsTo->set($key, $value, $duration); + $this->entries[$key] = $value; + } + + function clearStaleEntries() { + $this->pdo->exec('DELETE FROM cache WHERE expire != 0 AND expire < ' . $this->time); + } + + function get($key) { + if(isset($this->entries[$key])) { + return $this->entries[$key]; + } + + if(($starPos = strpos($key,'*')) === false) { + // Fetch Single + $this->getStatement->execute(array(':key' => $key)); + $entries = $this->getStatement->fetchAll(\PDO::FETCH_NUM); + } else { + // Prefetch With Wildcard + $this->getManyStatement->execute(array(':key' => substr($key,0,$starPos+1) . '%')); + $entries = $this->getManyStatement->fetchAll(\PDO::FETCH_NUM); + } + + $clearStaleEntries = false; + foreach($entries as $entry) { + if($entry[self::EXPIRE] == 0 || $entry[self::EXPIRE] <= $this->time) { + if(isset($entry[self::KEY])) { + $this->entries[$entry[self::KEY]] = unserialize($entry[self::VALUE]); + } else { + $this->entries[$key] = unserialize($entry[self::VALUE]); + } + } else { + $clearStaleEntries = true; + } + } + + if($clearStaleEntries) { + $this->clearStaleEntries(); + } + + if(isset($this->entries[$key])) { + return $this->entries[$key]; + } else{ + return $this->reportsTo->get($key); + } + } + + function delete($key) { + if($this->deleteStatement == null) { + $this->deleteStatement = $this->pdo->prepare('DELETE FROM cache WHERE key = :key OR (expire != 0 AND expire < ' . $this->time . ')'); + } + $this->deleteStatement->execute(array(':key' => $key)); + $this->reportsTo->delete($key); + } + + function clear() { + $this->pdo->exec('DELETE FROM cache'); + $this->reportsTo->clear(); + } +} +?> diff --git a/recess/recess/lang/AfterAnnotation.class.php b/recess/recess/lang/AfterAnnotation.class.php index 01bea71..3741ad4 100644 --- a/recess/recess/lang/AfterAnnotation.class.php +++ b/recess/recess/lang/AfterAnnotation.class.php @@ -1,75 +1,74 @@ -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); - } - } -} +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); + } + } +} ?> \ No newline at end of file diff --git a/recess/recess/lang/Annotation.class.php b/recess/recess/lang/Annotation.class.php index 7b77672..16e1467 100644 --- a/recess/recess/lang/Annotation.class.php +++ b/recess/recess/lang/Annotation.class.php @@ -1,296 +1,306 @@ - - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -abstract class Annotation { - - protected $errors = array(); - protected $values = array(); - - const FOR_CLASS = 1; - const FOR_METHOD = 2; - const FOR_PROPERTY = 4; - - /* Begin abstract methods */ - - /** - * Returns a string representation of the intended usage of an annotation. - * - * @return string - */ - abstract public function usage(); - - /** - * Returns an integer representation of the type(s) of PHP language constructs - * the annotation is applicable to. Use the Annotation::FOR_* consts to return - * the desired result. - * - * Examples: - * // Only valid on classes - * function isFor() { return Annotation::FOR_CLASS; } - * - * // Valid on methods or properties - * function isFor() { return Annotation::FOR_METHOD | Annotation::FOR_PROPERTY; } - * - * @return integer - */ - abstract public function isFor(); - - /** - * Validate is called just before expansion. Because there may be multiple - * constraints of an annotation the implementation of validate should append - * any error messages to the protected $errors property. Commonly used validations - * helper methods are provided as protected methods on the Annotation class. - * - * @param $class The classname the annotation is on. - */ - abstract protected function validate($class); - - /** - * The expansion step of an annotation gives it an opportunity to manipulate - * a class' descriptor by introducing additional metadata, attach methods, and - * wrap methods. - * - * @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. - */ - abstract protected function expand($class, $reflection, $descriptor); - - /* End abstract methods */ - - /* Begin validation helper methods */ - - protected function acceptedKeys($keys) { - foreach($this->parameters as $key => $value) { - if (is_string($key) && !in_array($key, $keys)) { - $this->errors[] = "Invalid parameter: \"$key\"."; - } - } - } - - protected function requiredKeys($keys) { - foreach($keys as $key) { - if(!array_key_exists($key, $this->parameters)) { - $this->errors[] = get_class($this) . " requires a '$key' parameter."; - } - } - } - - protected function acceptedKeylessValues($values) { - foreach($this->parameters as $key => $value) { - if(!is_string($key) && !in_array($value, $values)) { - $this->errors[] = "Unknown parameter: \"$value\"."; - } - } - } - - protected function acceptedIndexedValues($index, $values) { - if(!in_array($this->parameters[$index],$values)) { - $this->errors[] = "Parameter $index is set to \"" . $this->parameters[$key] . "\". Valid values: " . implode(', ', $values) . '.'; - } - } - - protected function acceptedValuesForKey($key, $values, $case = null) { - if(!isset($this->parameters[$key])) { return; } - - if($case === null) { - $value = $this->parameters[$key]; - } else if($case === CASE_LOWER) { - $value = strtolower($this->parameters[$key]); - } else if($case === CASE_UPPER) { - $value = strtoupper($this->parameters[$key]); - } - if(!in_array($value, $values)) { - $this->errors[] = 'The "' . $key . '" parameter is set to "' . $this->parameters[$key] . '". Valid values: ' . implode(', ', $values) . '.'; - } - } - - protected function acceptsNoKeylessValues() { - $this->acceptedKeylessValues(array()); - } - - protected function acceptsNoKeyedValues() { - $this->acceptedKeys(array()); - } - - protected function validOnSubclassesOf($annotatedClass, $baseClass) { - if( !is_subclass_of($annotatedClass, $baseClass) ) { - $this->errors[] = get_class($this) . " is only valid on objects of type $baseClass."; - } - } - - protected function minimumParameterCount($count) { - if( ! (count($this->parameters) >= $count) ) { - $this->errors[] = get_class($this) . " takes at least $count parameters."; - } - } - - protected function maximumParameterCount($count) { - if( ! (count($this->parameters) <= $count) ) { - $this->errors[] = get_class($this) . " takes at most $count parameters."; - } - } - - protected function exactParameterCount($count) { - if ( count($this->parameters) != $count ) { - $this->errors[] = get_class($this) . " requires exactly $count parameters."; - } - } - - /* End validation helper methods */ - - - function init($parameters) { - $this->parameters = array_change_key_case($parameters, CASE_LOWER); - } - - function isAValue($value) { - return in_array($value, $this->values); - } - - /** - * Mask other values to return the first not contained in the array. - * - * @param $values - * @return value not in the array of other values - */ - function valueNotIn($values) { - foreach($this->values as $value) { - if(!in_array($value, $values)) { - return $value; - } - } - return null; - } - - - function expandAnnotation($class, $reflection, $descriptor) { - // First check to ensure this annotation is allowed - // to apply to this type of PHP construct (class, method, property) - // using a simple bitwise mask. - if($reflection instanceof ReflectionClass) { - $annotationIsOn = self::FOR_CLASS; - $annotationIsOnType = 'class'; - } else if ($reflection instanceof ReflectionMethod) { - $annotationIsOn = self::FOR_METHOD; - $annotationIsOnType = 'method'; - } else if ($reflection instanceof ReflectionProperty) { - $annotationIsOn = self::FOR_PROPERTY; - $annotationIsOnType = 'property'; - } - if(!($annotationIsOn & $this->isFor())) { - $isFor = array(); - foreach(array('Classes' => self::FOR_CLASS, 'Methods' => self::FOR_METHOD, 'Properties' => self::FOR_PROPERTY) as $key => $mask) { - if($mask & $this->isFor()) { - $isFor[] = $key; - } - } - $this->errors[] = get_class($this) . ' is only valid on ' . implode(', ', $isFor) . '.'; - $typeError = true; - } else { - $typeError = false; - } - - // Run annotation specified validations - $this->validate($class); - - // Throw Exception if Annotation Errors Exist - if(!empty($this->errors)) { - if($reflection instanceof ReflectionProperty) { - $message = 'Invalid ' . get_class($this) . ' on property "' . $reflection->getName() . '". '; - $reflection = new ReflectionClass($class); - } else { - $message = 'Invalid ' . get_class($this) . ' on ' . $annotationIsOnType . ' "' . $reflection->getName() . '". '; - } - if(!$typeError) { - $message .= "Expected usage: \n" . $this->usage(); - } - $message .= "\n == Errors == \n * "; - $message .= implode("\n * ", $this->errors); - throw new RecessErrorException($message,0,0,$reflection->getFileName(),$reflection->getStartLine(),array()); - } - - // Map keyed parameters to properties on this annotation - // Place unkeyed parameters on the $this->values array - foreach($this->parameters as $key => $value) { - if(is_string($key)) { - $this->{$key} = $value; - } else { - $this->values[] = $value; - } - } - - // At this point we've processed the parameters, clearing memory - unset($this->parameters); - - // Finally dispatch to abstract method expand() so that - // Annotation developers can implement glorious new - // functionalities. - $this->expand($class, $reflection, $descriptor); - } - - - /** - * Given a docstring, returns an array of Recess Annotations. - * @param $docstring - * @return unknown_type - */ - static function parse($docstring) { - preg_match_all('%(?:\s|\*)*!(\S+)[^\n\r\S]*(?:(.*?)(?:\*/)|(.*))%', $docstring, $result, PREG_PATTERN_ORDER); - - $annotations = $result[1]; - if(isset($result[2][0]) && $result[2][0] != '') { - $values = $result[2]; - } else { - $values = $result[3]; - } - $returns = array(); - if(empty($result[1])) return array(); - foreach($annotations as $key => $annotation) { - // Strip Whitespace - $value = preg_replace('/\s*(\(|:|,|\))[^\n\r\S]*/', '${1}', '(' . $values[$key] . ')'); - // Extract Strings - preg_match_all('/\'(.*?)(?', $value); - - $value = vsprintf($value . ';', $quoted_strings); - - @eval('$array = ' . $value); - if(!isset($array)) { - throw new InvalidAnnotationValueException('There is an unparseable annotation value: "!' . $annotation . ': ' . $values[$key] . '"',0,0,'',0,array()); - } - - $annotationClass = $annotation . 'Annotation'; - $fullyQualified = Library::getFullyQualifiedClassName($annotationClass); - - if($annotationClass != $fullyQualified || class_exists($annotationClass,false)) { - $annotation = new $annotationClass; - $annotation->init($array); - } else { - throw new UnknownAnnotationException('Unknown annotation: "' . $annotation . '"',0,0,'',0,get_defined_vars()); - } - - $returns[] = $annotation; - } - unset($annotations,$values,$result); - return $returns; - } -} + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +abstract class Annotation { + + /** Begin 5.3 Namespace Work-around */ + static private $registeredAnnotations = array(); + static public function load() { + $fullClass = get_called_class(); + $class = explode('\\',$fullClass); + self::$registeredAnnotations[end($class)] = $fullClass; + } + /** End 5.3 Namespace Work-around */ + + protected $errors = array(); + protected $values = array(); + + const FOR_CLASS = 1; + const FOR_METHOD = 2; + const FOR_PROPERTY = 4; + + /* Begin abstract methods */ + + /** + * Returns a string representation of the intended usage of an annotation. + * + * @return string + */ + abstract public function usage(); + + /** + * Returns an integer representation of the type(s) of PHP language constructs + * the annotation is applicable to. Use the Annotation::FOR_* consts to return + * the desired result. + * + * Examples: + * // Only valid on classes + * function isFor() { return Annotation::FOR_CLASS; } + * + * // Valid on methods or properties + * function isFor() { return Annotation::FOR_METHOD | Annotation::FOR_PROPERTY; } + * + * @return integer + */ + abstract public function isFor(); + + /** + * Validate is called just before expansion. Because there may be multiple + * constraints of an annotation the implementation of validate should append + * any error messages to the protected $errors property. Commonly used validations + * helper methods are provided as protected methods on the Annotation class. + * + * @param $class The classname the annotation is on. + */ + abstract protected function validate($class); + + /** + * The expansion step of an annotation gives it an opportunity to manipulate + * a class' descriptor by introducing additional metadata, attach methods, and + * wrap methods. + * + * @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. + */ + abstract protected function expand($class, $reflection, $descriptor); + + /* End abstract methods */ + + /* Begin validation helper methods */ + + protected function acceptedKeys($keys) { + foreach($this->parameters as $key => $value) { + if (is_string($key) && !in_array($key, $keys)) { + $this->errors[] = "Invalid parameter: \"$key\"."; + } + } + } + + protected function requiredKeys($keys) { + foreach($keys as $key) { + if(!array_key_exists($key, $this->parameters)) { + $this->errors[] = get_class($this) . " requires a '$key' parameter."; + } + } + } + + protected function acceptedKeylessValues($values) { + foreach($this->parameters as $key => $value) { + if(!is_string($key) && !in_array($value, $values)) { + $this->errors[] = "Unknown parameter: \"$value\"."; + } + } + } + + protected function acceptedIndexedValues($index, $values) { + if(!in_array($this->parameters[$index],$values)) { + $this->errors[] = "Parameter $index is set to \"" . $this->parameters[$key] . "\". Valid values: " . implode(', ', $values) . '.'; + } + } + + protected function acceptedValuesForKey($key, $values, $case = null) { + if(!isset($this->parameters[$key])) { return; } + + if($case === null) { + $value = $this->parameters[$key]; + } else if($case === CASE_LOWER) { + $value = strtolower($this->parameters[$key]); + } else if($case === CASE_UPPER) { + $value = strtoupper($this->parameters[$key]); + } + if(!in_array($value, $values)) { + $this->errors[] = 'The "' . $key . '" parameter is set to "' . $this->parameters[$key] . '". Valid values: ' . implode(', ', $values) . '.'; + } + } + + protected function acceptsNoKeylessValues() { + $this->acceptedKeylessValues(array()); + } + + protected function acceptsNoKeyedValues() { + $this->acceptedKeys(array()); + } + + protected function validOnSubclassesOf($annotatedClass, $baseClass) { + if( !is_subclass_of($annotatedClass, $baseClass) ) { + $this->errors[] = get_class($this) . " is only valid on objects of type $baseClass."; + } + } + + protected function minimumParameterCount($count) { + if( ! (count($this->parameters) >= $count) ) { + $this->errors[] = get_class($this) . " takes at least $count parameters."; + } + } + + protected function maximumParameterCount($count) { + if( ! (count($this->parameters) <= $count) ) { + $this->errors[] = get_class($this) . " takes at most $count parameters."; + } + } + + protected function exactParameterCount($count) { + if ( count($this->parameters) != $count ) { + $this->errors[] = get_class($this) . " requires exactly $count parameters."; + } + } + + /* End validation helper methods */ + + + function init($parameters) { + $this->parameters = array_change_key_case($parameters, CASE_LOWER); + } + + function isAValue($value) { + return in_array($value, $this->values); + } + + /** + * Mask other values to return the first not contained in the array. + * + * @param $values + * @return value not in the array of other values + */ + function valueNotIn($values) { + foreach($this->values as $value) { + if(!in_array($value, $values)) { + return $value; + } + } + return null; + } + + + function expandAnnotation($class, $reflection, $descriptor) { + // First check to ensure this annotation is allowed + // to apply to this type of PHP construct (class, method, property) + // using a simple bitwise mask. + if($reflection instanceof ReflectionClass) { + $annotationIsOn = self::FOR_CLASS; + $annotationIsOnType = 'class'; + } else if ($reflection instanceof ReflectionMethod) { + $annotationIsOn = self::FOR_METHOD; + $annotationIsOnType = 'method'; + } else if ($reflection instanceof ReflectionProperty) { + $annotationIsOn = self::FOR_PROPERTY; + $annotationIsOnType = 'property'; + } + if(!($annotationIsOn & $this->isFor())) { + $isFor = array(); + foreach(array('Classes' => self::FOR_CLASS, 'Methods' => self::FOR_METHOD, 'Properties' => self::FOR_PROPERTY) as $key => $mask) { + if($mask & $this->isFor()) { + $isFor[] = $key; + } + } + $this->errors[] = get_class($this) . ' is only valid on ' . implode(', ', $isFor) . '.'; + $typeError = true; + } else { + $typeError = false; + } + + // Run annotation specified validations + $this->validate($class); + + // Throw Exception if Annotation Errors Exist + if(!empty($this->errors)) { + if($reflection instanceof ReflectionProperty) { + $message = 'Invalid ' . get_class($this) . ' on property "' . $reflection->getName() . '". '; + $reflection = new ReflectionClass($class); + } else { + $message = 'Invalid ' . get_class($this) . ' on ' . $annotationIsOnType . ' "' . $reflection->getName() . '". '; + } + if(!$typeError) { + $message .= "Expected usage: \n" . $this->usage(); + } + $message .= "\n == Errors == \n * "; + $message .= implode("\n * ", $this->errors); + throw new ErrorException($message,0,0,$reflection->getFileName(),$reflection->getStartLine(),array()); + } + + // Map keyed parameters to properties on this annotation + // Place unkeyed parameters on the $this->values array + foreach($this->parameters as $key => $value) { + if(is_string($key)) { + $this->{$key} = $value; + } else { + $this->values[] = $value; + } + } + + // At this point we've processed the parameters, clearing memory + unset($this->parameters); + + // Finally dispatch to abstract method expand() so that + // Annotation developers can implement glorious new + // functionalities. + $this->expand($class, $reflection, $descriptor); + } + + + /** + * Given a docstring, returns an array of Recess Annotations. + * @param $docstring + * @return unknown_type + */ + static function parse($docstring) { + preg_match_all('%(?:\s|\*)*!(\S+)[^\n\r\S]*(?:(.*?)(?:\*/)|(.*))%', $docstring, $result, PREG_PATTERN_ORDER); + + $annotations = $result[1]; + if(isset($result[2][0]) && $result[2][0] != '') { + $values = $result[2]; + } else { + $values = $result[3]; + } + $returns = array(); + if(empty($result[1])) return array(); + foreach($annotations as $key => $annotation) { + // Strip Whitespace + $value = preg_replace('/\s*(\(|:|,|\))[^\n\r\S]*/', '${1}', '(' . $values[$key] . ')'); + // Extract Strings + preg_match_all('/\'(.*?)(?', $value); + + $value = vsprintf($value . ';', $quoted_strings); + + @eval('$array = ' . $value); + if(!isset($array)) { + throw new InvalidAnnotationValueException('There is an unparseable annotation value: "!' . $annotation . ': ' . $values[$key] . '"',0,0,'',0,array()); + } + + $annotationClass = $annotation . 'Annotation'; + if(isset(self::$registeredAnnotations[$annotationClass])) { + $annotation = new self::$registeredAnnotations[$annotationClass]; + $annotation->init($array); + } else { + print_r(self::$registeredAnnotations); + throw new UnknownAnnotationException('Unknown annotation: "' . $annotation . '"',0,0,'',0,get_defined_vars()); + } + + $returns[] = $annotation; + } + unset($annotations,$values,$result); + return $returns; + } +} ?> \ No newline at end of file diff --git a/recess/recess/lang/AttachedMethod.class.php b/recess/recess/lang/AttachedMethod.class.php index 27fd79b..d72280b 100644 --- a/recess/recess/lang/AttachedMethod.class.php +++ b/recess/recess/lang/AttachedMethod.class.php @@ -1,48 +1,49 @@ - - */ -class AttachedMethod { - public $object; - public $method; - public $name; - - function __construct($object, $method, $name) { - $this->object = $object; - $this->method = $method; - $this->name = $name; - } - - function isFinal() { return true; } - function isAbstract() { return false; } - function isPublic() { return true; } - function isPrivate() { return false; } - function isProtected() { return false; } - function isStatic() { return false; } - function isConstructor() { return false; } - function isDestructor() { return false; } - function isAttached() { return true; } - - function getName() { return $this->alias; } - function isInternal() { return false; } - function isUserDefined() { return true; } - - function getFileName() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getFileName(); } - function getStartLine() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getStartLine(); } - function getEndLine() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getEndLine(); } - function getParameters() { - $reflection = new ReflectionClass($this->object); - $params = $reflection->getMethod($this->method)->getParameters(); - array_shift($params); - return $params; - } - function getNumberOfParameters() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getNumberOfParameters() - 1; } - function getNumberOfRequiredParameters() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getNumberOfRequiredParameters() - 1; } -} - + + */ +class AttachedMethod { + public $object; + public $method; + public $name; + + function __construct($object, $method, $name) { + $this->object = $object; + $this->method = $method; + $this->name = $name; + } + + function isFinal() { return true; } + function isAbstract() { return false; } + function isPublic() { return true; } + function isPrivate() { return false; } + function isProtected() { return false; } + function isStatic() { return false; } + function isConstructor() { return false; } + function isDestructor() { return false; } + function isAttached() { return true; } + + function getName() { return $this->alias; } + function isInternal() { return false; } + function isUserDefined() { return true; } + + function getFileName() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getFileName(); } + function getStartLine() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getStartLine(); } + function getEndLine() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getEndLine(); } + function getParameters() { + $reflection = new ReflectionClass($this->object); + $params = $reflection->getMethod($this->method)->getParameters(); + array_shift($params); + return $params; + } + function getNumberOfParameters() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getNumberOfParameters() - 1; } + function getNumberOfRequiredParameters() { $reflection = new ReflectionClass($this->object); return $reflection->getMethod($this->method)->getNumberOfRequiredParameters() - 1; } +} + ?> \ No newline at end of file diff --git a/recess/recess/lang/BeforeAnnotation.class.php b/recess/recess/lang/BeforeAnnotation.class.php index 0d7bbbd..c91b7a5 100644 --- a/recess/recess/lang/BeforeAnnotation.class.php +++ b/recess/recess/lang/BeforeAnnotation.class.php @@ -1,73 +1,72 @@ -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); - } - } -} +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); + } + } +} ?> \ No newline at end of file diff --git a/recess/recess/lang/ClassDescriptor.class.php b/recess/recess/lang/ClassDescriptor.class.php index 4f5961e..639c61a 100644 --- a/recess/recess/lang/ClassDescriptor.class.php +++ b/recess/recess/lang/ClassDescriptor.class.php @@ -1,91 +1,90 @@ - - */ -class ClassDescriptor { - - protected $attachedMethods = array(); - protected $wrappedMethods = array(); - - /** - * Return a RecessAttachedMethod for given name, or return false. - * - * @param string $methodName Method name. - * @return RecessAttachedMethod on success, false on failure. - */ - function getAttachedMethod($methodName) { - if(isset($this->attachedMethods[$methodName])) - return $this->attachedMethods[$methodName]; - else - return false; - } - - /** - * Return all attached methods. - * - * @return array(AttachedMethod) - */ - function getAttachedMethods() { - return $this->attachedMethods; - } - - /** - * Add an attached method with given methodName alias. - * - * @param string $methodName - * @param AttachedMethod $attachedMethod - */ - function addAttachedMethod($methodName, AttachedMethod $attachedMethod) { - $this->attachedMethods[$methodName] = $attachedMethod; - } - - /** - * 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. - * - * @param string $attachOnClassName - * @param string $attachedMethodAlias - * @param object $providerInstance - * @param string $providerMethodName - */ - function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) { - $attachedMethod = new AttachedMethod($providerInstance, $providerMethodName, $attachedMethodAlias); - $this->addAttachedMethod($attachedMethodAlias, $attachedMethod); - } - - /** - * 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; - } - } -} + + */ +class ClassDescriptor { + + protected $attachedMethods = array(); + protected $wrappedMethods = array(); + + /** + * Return a RecessAttachedMethod for given name, or return false. + * + * @param string $methodName Method name. + * @return RecessAttachedMethod on success, false on failure. + */ + function getAttachedMethod($methodName) { + if(isset($this->attachedMethods[$methodName])) + return $this->attachedMethods[$methodName]; + else + return false; + } + + /** + * Return all attached methods. + * + * @return array(AttachedMethod) + */ + function getAttachedMethods() { + return $this->attachedMethods; + } + + /** + * Add an attached method with given methodName alias. + * + * @param string $methodName + * @param AttachedMethod $attachedMethod + */ + function addAttachedMethod($methodName, AttachedMethod $attachedMethod) { + $this->attachedMethods[$methodName] = $attachedMethod; + } + + /** + * 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. + * + * @param string $attachOnClassName + * @param string $attachedMethodAlias + * @param object $providerInstance + * @param string $providerMethodName + */ + function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) { + $attachedMethod = new AttachedMethod($providerInstance, $providerMethodName, $attachedMethodAlias); + $this->addAttachedMethod($attachedMethodAlias, $attachedMethod); + } + + /** + * 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/ErrorException.class.php b/recess/recess/lang/ErrorException.class.php new file mode 100644 index 0000000..085681f --- /dev/null +++ b/recess/recess/lang/ErrorException.class.php @@ -0,0 +1,11 @@ +context = $context; + } +} \ No newline at end of file diff --git a/recess/recess/lang/Exception.class.php b/recess/recess/lang/Exception.class.php new file mode 100644 index 0000000..318ffb4 --- /dev/null +++ b/recess/recess/lang/Exception.class.php @@ -0,0 +1,15 @@ +context = $context; + } + + public function getRecessTrace() { + return $this->getTrace(); + } +} \ No newline at end of file diff --git a/recess/recess/lang/IWrapper.class.php b/recess/recess/lang/IWrapper.class.php index ffc08e8..e1e6eae 100644 --- a/recess/recess/lang/IWrapper.class.php +++ b/recess/recess/lang/IWrapper.class.php @@ -1,47 +1,49 @@ - - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -interface IWrapper { - - /** - * Called before the wrapped method is called. Before is given a chance to - * massage the arguments being passed to the wrapped method, throw an exception, - * or short-circuit a return value without the wrapped method being called. - * - * @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); - - /** - * Called after the wrapped method is called. After is given a chance to - * post-process the return value of the wrapped method. If a return value from - * 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($object, $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 $wrapper); - -} + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +interface IWrapper { + + /** + * Called before the wrapped method is called. Before is given a chance to + * massage the arguments being passed to the wrapped method, throw an exception, + * or short-circuit a return value without the wrapped method being called. + * + * @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); + + /** + * Called after the wrapped method is called. After is given a chance to + * post-process the return value of the wrapped method. If a return value from + * 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($object, $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 $wrapper); + +} ?> \ No newline at end of file diff --git a/recess/recess/lang/MethodCallWrapper.class.php b/recess/recess/lang/MethodCallWrapper.class.php index b379ee6..d5634cd 100644 --- a/recess/recess/lang/MethodCallWrapper.class.php +++ b/recess/recess/lang/MethodCallWrapper.class.php @@ -1,106 +1,106 @@ -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; - } - } -} +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 ReflectionClass($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 ReflectionClass($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; + } + } +} ?> \ No newline at end of file diff --git a/recess/recess/lang/Object.class.php b/recess/recess/lang/Object.class.php index ddfaa05..1d239ff 100644 --- a/recess/recess/lang/Object.class.php +++ b/recess/recess/lang/Object.class.php @@ -1,266 +1,263 @@ -foo(); - * - * Example usage of wrappable methods and a hypothetical "EchoWrapper" which - * wraps a method by echo'ing strings before and after. - * - * class Model extends Object { - * /** !Wrappable insert * / - * function wrappedInsert() { echo "Wrapped (insert)"; } - * } - * - * /** !EchoWrapper insert, Before: "Hello", After: "World" * / - * class Person extends Model {} - * - * $person = new Person(); - * $person->insert(); - * - * // Output: - * Hello - * Wrapped (insert) - * World - * - * @author Kris Jordan - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -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. - * - * @param string $attachOnClassName - * @param string $attachedMethodAlias - * @param object $providerInstance - * @param string $providerMethodName - */ - static function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) { - self::getClassDescriptor($attachOnClassName)->attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName); - } - - /** - * 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. - * - * @param string $wrapOnClassName - * @param string $wrappableMethodName - * @param IWrapper $wrapper - */ - static function wrapMethod($wrapOnClassName, $wrappableMethodName, IWrapper $wrapper) { - self::getClassDescriptor($wrapOnClassName)->addWrapper($wrappableMethodName, $wrapper); - } - - /** - * Dynamic dispatch of function calls to attached methods. - * - * @param string $name - * @param array $arguments - * @return variant - */ - final function __call($name, $arguments) { - $classDescriptor = self::getClassDescriptor($this); - - $attachedMethod = $classDescriptor->getAttachedMethod($name); - if($attachedMethod !== false) { - $object = $attachedMethod->object; - $method = $attachedMethod->method; - array_unshift($arguments, $this); - $reflectedMethod = new ReflectionMethod($object, $method); - return $reflectedMethod->invokeArgs($object, $arguments); - } else { - throw new RecessException('"' . get_class($this) . '" class does not contain a method or an attached method named "' . $name . '".', get_defined_vars()); - } - } - - const RECESS_CLASS_KEY_PREFIX = 'Object::desc::'; - - /** - * Return the ObjectInfo for provided Object instance. - * - * @param variant $classNameOrInstance - String Class Name or Instance of Recess Class - * @return ClassDescriptor - */ - final static protected function getClassDescriptor($classNameOrInstance) { - if($classNameOrInstance instanceof Object) { - $class = get_class($classNameOrInstance); - $instance = $classNameOrInstance; - } else { - $class = $classNameOrInstance; - if(class_exists($class, true)) { - $reflectionClass = new ReflectionClass($class); - if(!$reflectionClass->isAbstract()) { - $instance = new $class; - } else { - return new ClassDescriptor(); - } - } - } - - if(!isset(self::$descriptors[$class])) { - $cache_key = self::RECESS_CLASS_KEY_PREFIX . $class; - $descriptor = Cache::get($cache_key); - - if($descriptor === false) { - if($instance instanceof Object) { - $descriptor = call_user_func(array($class, 'buildClassDescriptor'), $class); - - Cache::set($cache_key, $descriptor); - self::$descriptors[$class] = $descriptor; - } else { - throw new RecessException('ObjectRegistry only retains information on classes derived from Object. Class of type "' . $class . '" given.', get_defined_vars()); - } - } else { - self::$descriptors[$class] = $descriptor; - } - } - - return self::$descriptors[$class]; - } - - /** - * Retrieve an array of the attached methods for a particular class. - * - * @param variant $classNameOrInstance - String class name or instance of a Recess Class - * @return array - */ - final static function getAttachedMethods($classNameOrInstance) { - $descriptor = self::getClassDescriptor($classNameOrInstance); - return $descriptor->getAttachedMethods(); - } - - /** - * Clear the descriptors cache. - */ - final static function clearDescriptors() { - self::$descriptors = array(); - } - - /** - * Initialize a class' descriptor. Override to return a subclass specific descriptor. - * A subclass's descriptor may need to initialize certain properties. For example - * Model's descriptor has properties initialized for table, primary key, etc. The controller - * descriptor has a routes array initialized as empty. - * - * @param $class string Name of class whose descriptor is being initialized. - * @return ClassDescriptor - */ - protected static function initClassDescriptor($class) { return new ClassDescriptor(); } - - /** - * Prior to expanding the annotations for a class method this hook is called to give - * a subclass an opportunity to manipulate its descriptor. For example Controller - * uses this in able to create default routes for methods which do not have explicit - * Route annotations. - * - * @param $class string Name of class whose descriptor is being initialized. - * @param $method ReflectionMethod - * @param $descriptor ClassDescriptor - * @param $annotations Array of annotations found on method. - * @return ClassDescriptor - */ - protected static function shapeDescriptorWithMethod($class, $method, $descriptor, $annotations) { return $descriptor; } - - /** - * Prior to expanding the annotations for a class property this hook is called to give - * a subclass an opportunity to manipulate its class descriptor. For example Model - * uses this to initialize the datastructure for a Property before a Column annotation - * applies metadata. - * - * @param $class string Name of class whose descriptor is being initialized. - * @param $property ReflectionProperty - * @param $descriptor ClassDescriptor - * @param $annotations Array of annotations found on method. - * @return ClassDescriptor - */ - protected static function shapeDescriptorWithProperty($class, $property, $descriptor, $annotations) { return $descriptor; } - - /** - * After all methods and properties of a class have been visited and annotations expanded - * this hook provides a sub-class a final opportunity to do post-processing and sanitization. - * For example, Model uses this hook to ensure consistency between model's descriptor - * and the actual database's columns. - * - * @param $class - * @param $descriptor - * @return ClassDescriptor - */ - protected static function finalClassDescriptor($class, $descriptor) { return $descriptor; } - - /** - * Builds a class' metadata structure (Class Descriptor through reflection - * and expansion of annotations. Hooks are provided in a Strategy Pattern-like - * fashion to allow subclasses to influence various points in the pipeline of - * building a class descriptor (initialization, discovery of method, discovery of - * property, finalization). - * - * @param $class Name of class whose descriptor is being built. - * @return ClassDescriptor - */ - protected static function buildClassDescriptor($class) { - $descriptor = call_user_func(array($class, 'initClassDescriptor'), $class); - - try { - $reflection = new RecessReflectionClass($class); - } catch(ReflectionException $e) { - throw new RecessException('Class "' . $class . '" has not been declared.', get_defined_vars()); - } - - foreach ($reflection->getAnnotations() as $annotation) { - $annotation->expandAnnotation($class, $reflection, $descriptor); - } - - foreach($reflection->getMethods(false) as $method) { - $annotations = $method->getAnnotations(); - $descriptor = call_user_func(array($class, 'shapeDescriptorWithMethod'), $class, $method, $descriptor, $annotations); - foreach($annotations as $annotation) { - $annotation->expandAnnotation($class, $method, $descriptor); - } - } - - foreach($reflection->getProperties(false) as $property) { - $annotations = $property->getAnnotations(); - $descriptor = call_user_func(array($class, 'shapeDescriptorWithProperty'), $class, $property, $descriptor, $annotations); - foreach($annotations as $annotation) { - $annotation->expandAnnotation($class, $property, $descriptor); - } - } - - $descriptor = call_user_func(array($class, 'finalClassDescriptor'), $class, $descriptor); - - return $descriptor; - } - -} - -?> \ No newline at end of file +foo(); + * + * Example usage of wrappable methods and a hypothetical "EchoWrapper" which + * wraps a method by echo'ing strings before and after. + * + * class Model extends Object { + * /** !Wrappable insert * / + * function wrappedInsert() { echo "Wrapped (insert)"; } + * } + * + * /** !EchoWrapper insert, Before: "Hello", After: "World" * / + * class Person extends Model {} + * + * $person = new Person(); + * $person->insert(); + * + * // Output: + * Hello + * Wrapped (insert) + * World + * + * @author Kris Jordan + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +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. + * + * @param string $attachOnClassName + * @param string $attachedMethodAlias + * @param object $providerInstance + * @param string $providerMethodName + */ + static function attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName) { + self::getClassDescriptor($attachOnClassName)->attachMethod($attachOnClassName, $attachedMethodAlias, $providerInstance, $providerMethodName); + } + + /** + * 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. + * + * @param string $wrapOnClassName + * @param string $wrappableMethodName + * @param IWrapper $wrapper + */ + static function wrapMethod($wrapOnClassName, $wrappableMethodName, IWrapper $wrapper) { + self::getClassDescriptor($wrapOnClassName)->addWrapper($wrappableMethodName, $wrapper); + } + + /** + * Dynamic dispatch of function calls to attached methods. + * + * @param string $name + * @param array $arguments + * @return variant + */ + final function __call($name, $arguments) { + $classDescriptor = self::getClassDescriptor($this); + + $attachedMethod = $classDescriptor->getAttachedMethod($name); + if($attachedMethod !== false) { + $object = $attachedMethod->object; + $method = $attachedMethod->method; + array_unshift($arguments, $this); + $reflectedMethod = new \ReflectionMethod($object, $method); + return $reflectedMethod->invokeArgs($object, $arguments); + } else { + throw new Exception('"' . get_class($this) . '" class does not contain a method or an attached method named "' . $name . '".', get_defined_vars()); + } + } + + const RECESS_CLASS_KEY_PREFIX = 'Object::desc::'; + + /** + * Return the ObjectInfo for provided Object instance. + * + * @param variant $classNameOrInstance - String Class Name or Instance of Recess Class + * @return ClassDescriptor + */ + final static protected function getClassDescriptor($classNameOrInstance) { + if($classNameOrInstance instanceof Object) { + $class = get_class($classNameOrInstance); + $instance = $classNameOrInstance; + } else { + $class = $classNameOrInstance; + if(class_exists($class, true)) { + $reflectionClass = new ReflectionClass($class); + if(!$reflectionClass->isAbstract()) { + $instance = new $class; + } else { + return new ClassDescriptor(); + } + } + } + + if(!isset(self::$descriptors[$class])) { + $cache_key = self::RECESS_CLASS_KEY_PREFIX . $class; + $descriptor = Cache::get($cache_key); + + if($descriptor === false) { + if($instance instanceof Object) { + $descriptor = call_user_func(array($class, 'buildClassDescriptor'), $class); + + Cache::set($cache_key, $descriptor); + self::$descriptors[$class] = $descriptor; + } else { + throw new Exception('ObjectRegistry only retains information on classes derived from Object. Class of type "' . $class . '" given.', get_defined_vars()); + } + } else { + self::$descriptors[$class] = $descriptor; + } + } + + return self::$descriptors[$class]; + } + + /** + * Retrieve an array of the attached methods for a particular class. + * + * @param variant $classNameOrInstance - String class name or instance of a Recess Class + * @return array + */ + final static function getAttachedMethods() { + $descriptor = static::getClassDescriptor(get_called_class()); + return $descriptor->getAttachedMethods(); + } + + /** + * Clear the descriptors cache. + */ + final static function clearDescriptors() { + self::$descriptors = array(); + } + + /** + * Initialize a class' descriptor. Override to return a subclass specific descriptor. + * A subclass's descriptor may need to initialize certain properties. For example + * Model's descriptor has properties initialized for table, primary key, etc. The controller + * descriptor has a routes array initialized as empty. + * + * @param $class string Name of class whose descriptor is being initialized. + * @return ClassDescriptor + */ + protected static function initClassDescriptor() { return new ClassDescriptor(); } + + /** + * Prior to expanding the annotations for a class method this hook is called to give + * a subclass an opportunity to manipulate its descriptor. For example Controller + * uses this in able to create default routes for methods which do not have explicit + * Route annotations. + * + * @param $class string Name of class whose descriptor is being initialized. + * @param $method ReflectionMethod + * @param $descriptor ClassDescriptor + * @param $annotations Array of annotations found on method. + * @return ClassDescriptor + */ + protected static function shapeDescriptorWithMethod($method, $descriptor, $annotations) { return $descriptor; } + + /** + * Prior to expanding the annotations for a class property this hook is called to give + * a subclass an opportunity to manipulate its class descriptor. For example Model + * uses this to initialize the datastructure for a Property before a Column annotation + * applies metadata. + * + * @param $class string Name of class whose descriptor is being initialized. + * @param $property ReflectionProperty + * @param $descriptor ClassDescriptor + * @param $annotations Array of annotations found on method. + * @return ClassDescriptor + */ + protected static function shapeDescriptorWithProperty($property, $descriptor, $annotations) { return $descriptor; } + + /** + * After all methods and properties of a class have been visited and annotations expanded + * this hook provides a sub-class a final opportunity to do post-processing and sanitization. + * For example, Model uses this hook to ensure consistency between model's descriptor + * and the actual database's columns. + * + * @param $class + * @param $descriptor + * @return ClassDescriptor + */ + protected static function finalClassDescriptor($descriptor) { return $descriptor; } + + /** + * Builds a class' metadata structure (Class Descriptor through reflection + * and expansion of annotations. Hooks are provided in a Strategy Pattern-like + * fashion to allow subclasses to influence various points in the pipeline of + * building a class descriptor (initialization, discovery of method, discovery of + * property, finalization). + * + * @param $class Name of class whose descriptor is being built. + * @return ClassDescriptor + */ + protected static function buildClassDescriptor() { + $class = get_called_class(); + $descriptor = static::initClassDescriptor(); + + try { + $reflection = new ReflectionClass($class); + } catch(\ReflectionException $e) { + throw new Exception('Class "' . $class . '" has not been declared.', get_defined_vars()); + } + + foreach ($reflection->getAnnotations() as $annotation) { + $annotation->expandAnnotation($class, $reflection, $descriptor); + } + + foreach($reflection->getMethods(false) as $method) { + $annotations = $method->getAnnotations(); + $descriptor = static::shapeDescriptorWithMethod($method, $descriptor, $annotations); + foreach($annotations as $annotation) { + $annotation->expandAnnotation($class, $method, $descriptor); + } + } + + foreach($reflection->getProperties(false) as $property) { + $annotations = $property->getAnnotations(); + $descriptor = static::shapeDescriptorWithProperty($property, $descriptor, $annotations); + foreach($annotations as $annotation) { + $annotation->expandAnnotation($class, $property, $descriptor); + } + } + + $descriptor = static::finalClassDescriptor($descriptor); + + return $descriptor; + } + +} \ No newline at end of file diff --git a/recess/recess/lang/ReflectionClass.class.php b/recess/recess/lang/ReflectionClass.class.php new file mode 100644 index 0000000..3f5d011 --- /dev/null +++ b/recess/recess/lang/ReflectionClass.class.php @@ -0,0 +1,65 @@ + HasManyAnnotation + * + * This class is instantiated if it exists (else throws UnknownAnnotationException) and its init + * method is passed the value array following the annotation's name. + * + * @todo Harden the regular expressions. + * + * @author Kris Jordan + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class ReflectionClass extends \ReflectionClass { + function getProperties($filter = null) { + $rawProperties = parent::getProperties(); + $properties = array(); + foreach($rawProperties as $rawProperty) { + $properties[] = new ReflectionProperty($this->name, $rawProperty->name); + } + return $properties; + } + function getMethods($getAttachedMethods = true){ + $rawMethods = parent::getMethods(); + $methods = array(); + foreach($rawMethods as $rawMethod) { + $method = new ReflectionMethod($this->name, $rawMethod->name); + $methods[] = $method; + } + + if($getAttachedMethods && is_subclass_of($this->name, 'Object')) { + $methods = array_merge($methods, Object::getAttachedMethods($this->name)); + } + + return $methods; + } + function getAnnotations() { + $docstring = $this->getDocComment(); + if($docstring == '') return array(); + else { + $returns = array(); + try { + $returns = Annotation::parse($docstring); + } catch(InvalidAnnotationValueException $e) { + throw new InvalidAnnotationValueException('In class "' . $this->name . '".' . $e->getMessage(),0,0,$this->getFileName(),$this->getStartLine(),array()); + } catch(UnknownAnnotationException $e) { + throw new UnknownAnnotationException('In class "' . $this->name . '".' . $e->getMessage(),0,0,$this->getFileName(),$this->getStartLine(),array()); + } + } + return $returns; + } +} \ No newline at end of file diff --git a/recess/recess/lang/ReflectionMethod.class.php b/recess/recess/lang/ReflectionMethod.class.php new file mode 100644 index 0000000..0bfb35a --- /dev/null +++ b/recess/recess/lang/ReflectionMethod.class.php @@ -0,0 +1,35 @@ + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class ReflectionMethod extends \ReflectionMethod { + function getAnnotations() { + $docstring = $this->getDocComment(); + if($docstring == '') return array(); + else { + $returns = array(); + try { + $returns = Annotation::parse($docstring); + } catch(InvalidAnnotationValueException $e) { + throw new InvalidAnnotationValueException('In class "' . $this->getDeclaringClass()->name . '" on method "'. $this->name .'".' . $e->getMessage(),0,0,$this->getFileName(),$this->getStartLine(),array()); + } catch(UnknownAnnotationException $e) { + throw new UnknownAnnotationException('In class "' . $this->getDeclaringClass()->name . '" on method "'. $this->name .'".' . $e->getMessage(),0,0,$this->getFileName(),$this->getStartLine(),array()); + } + } + return $returns; + } + + function isAttached() { + return false; + } +} +?> \ No newline at end of file diff --git a/recess/recess/lang/ReflectionProperty.class.php b/recess/recess/lang/ReflectionProperty.class.php new file mode 100644 index 0000000..e2c387b --- /dev/null +++ b/recess/recess/lang/ReflectionProperty.class.php @@ -0,0 +1,33 @@ + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + * @todo Add custom getFileName() and getStartLine() methods + */ +class ReflectionProperty extends \ReflectionProperty { + function getAnnotations() { + $docstring = $this->getDocComment(); + if($docstring == '') return array(); + else { + $returns = array(); + try { + $returns = Annotation::parse($docstring); + } catch(InvalidAnnotationValueException $e) { + throw new InvalidAnnotationValueException('In class "' . $this->getDeclaringClass()->name . '" on property "'. $this->name .'".' . $e->getMessage(),0,0,$this->getDeclaringClass()->getFileName(),$this->getDeclaringClass()->getStartLine(),array()); + } catch(UnknownAnnotationException $e) { + throw new UnknownAnnotationException('In class "' . $this->getDeclaringClass()->name . '" on property "'. $this->name .'".' . $e->getMessage(),0,0,$this->getDeclaringClass()->getFileName(),$this->getDeclaringClass()->getStartLine(),array()); + } + } + return $returns; + } +} + +?> \ No newline at end of file diff --git a/recess/recess/lang/WrappableAnnotation.class.php b/recess/recess/lang/WrappableAnnotation.class.php index 02d732a..9637cbf 100644 --- a/recess/recess/lang/WrappableAnnotation.class.php +++ b/recess/recess/lang/WrappableAnnotation.class.php @@ -1,63 +1,62 @@ -test('bar'); - * // > Before test("bar") - * // > bar - * // > After test() returns: fooz - * echo $result - * // > baz - * - * Key methods in the framework are made Wrappable so that functionality can - * easily be plugged into Recess. - * - * - * @author Kris Jordan - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -class WrappableAnnotation extends Annotation { - - public function usage() { - return '!Wrappable wrappedMethodName'; - } - - protected function validate($class) { - $this->exactParameterCount(1); - } - - public function isFor() { - return Annotation::FOR_METHOD; - } - - protected function expand($class, $reflection, $descriptor) { - $methodName = $this->values[0]; - - $wrappedMethod = new WrappedMethod($reflection->name); - - $descriptor->attachMethod($class, $methodName, $wrappedMethod, WrappedMethod::CALL); - - $descriptor->addWrappedMethod($methodName, $wrappedMethod); - } - -} +test('bar'); + * // > Before test("bar") + * // > bar + * // > After test() returns: fooz + * echo $result + * // > baz + * + * Key methods in the framework are made Wrappable so that functionality can + * easily be plugged into Recess. + * + * + * @author Kris Jordan + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class WrappableAnnotation extends Annotation { + + public function usage() { + return '!Wrappable wrappedMethodName'; + } + + protected function validate($class) { + $this->exactParameterCount(1); + } + + public function isFor() { + return Annotation::FOR_METHOD; + } + + protected function expand($class, $reflection, $descriptor) { + $methodName = $this->values[0]; + + $wrappedMethod = new WrappedMethod($reflection->name); + + $descriptor->attachMethod($class, $methodName, $wrappedMethod, WrappedMethod::CALL); + + $descriptor->addWrappedMethod($methodName, $wrappedMethod); + } + +} ?> \ No newline at end of file diff --git a/recess/recess/lang/WrappedMethod.class.php b/recess/recess/lang/WrappedMethod.class.php index aafaa26..27bf52a 100644 --- a/recess/recess/lang/WrappedMethod.class.php +++ b/recess/recess/lang/WrappedMethod.class.php @@ -1,119 +1,118 @@ - - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -class WrappedMethod { - - const CALL = 'call'; - - /** - * Registered wrappers whose callbacks are invoked during call() - * @var array(IWrapper) - */ - protected $wrappers = array(); - - /** - * Name of the method to invoke on the object passed to call() - * @var string - */ - public $method; - - /** - * The constructor takes an optional method name. - * - * @param String $method name of the method to invoke - */ - function __construct($method = '') { - $this->method = $method; - } - - /** - * Wrap the method with another IWrapper - * - * @param IWrapper $wrapper - */ - function addWrapper(IWrapper $wrapper) { - foreach($this->wrappers as $existingWrapper) { - if($existingWrapper->combine($wrapper)) { - return; - } - } - $this->wrappers[] = $wrapper; - } - - /** - * Assume takes the wrappers from another WrappedMethod - * and makes them their own. Assume is necessary because - * the actual WrappedMethod in a ClassDescriptor may not exist - * prior to it needing to be wrapped (by a Class-level annotation) - * so we create a place-holder WrappedMethod until the actual - * wrapped method Assumes its rightful place. - * - * @param WrappedMethod $wrappedMethod the place-holder WrappedMethod. - * @return WrappedMethod - */ - function assume(WrappedMethod $wrappedMethod) { - $this->wrappers = $wrappedMethod->wrappers; - return $this; - } - - /** - * The wrappers and wrapped method are invoked in call(). - * First the before() methods of wrappers are invoked in the order - * in which the Wrappers were applied. The before methods are passed - * the arguments which will eventually be passed to the actual wrapped - * method by reference for possible manipulation. If a before method - * returns a value this value is short-circuits the calling process - * and returns that value immediately. Next the wrapped method is - * invoked. The result is then passed to the after() methods of wrappers - * in the reverse order in which they were applied. The after methods - * can manipulate the returned value. - * - * @return mixed - */ - function call() { - $args = func_get_args(); - - $object = array_shift($args); - - foreach($this->wrappers as $wrapper) { - $returns = $wrapper->before($object, $args); - if($returns !== null) { - // Short-circuit return - return $returns; - } - } - - if(!isset($this->reflectedMethod)) { - $this->reflectedMethod = new ReflectionMethod($object, $this->method); - } - - $returns = $this->reflectedMethod->invokeArgs($object, $args); - - foreach(array_reverse($this->wrappers) as $wrapper) { - $wrapperReturn = $wrapper->after($object, $returns); - if($wrapperReturn !== null) { - $returns = $wrapperReturn; - } - } - - return $returns; - } -} + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class WrappedMethod { + + const CALL = 'call'; + + /** + * Registered wrappers whose callbacks are invoked during call() + * @var array(IWrapper) + */ + protected $wrappers = array(); + + /** + * Name of the method to invoke on the object passed to call() + * @var string + */ + public $method; + + /** + * The constructor takes an optional method name. + * + * @param String $method name of the method to invoke + */ + function __construct($method = '') { + $this->method = $method; + } + + /** + * Wrap the method with another IWrapper + * + * @param IWrapper $wrapper + */ + function addWrapper(IWrapper $wrapper) { + foreach($this->wrappers as $existingWrapper) { + if($existingWrapper->combine($wrapper)) { + return; + } + } + $this->wrappers[] = $wrapper; + } + + /** + * Assume takes the wrappers from another WrappedMethod + * and makes them their own. Assume is necessary because + * the actual WrappedMethod in a ClassDescriptor may not exist + * prior to it needing to be wrapped (by a Class-level annotation) + * so we create a place-holder WrappedMethod until the actual + * wrapped method Assumes its rightful place. + * + * @param WrappedMethod $wrappedMethod the place-holder WrappedMethod. + * @return WrappedMethod + */ + function assume(WrappedMethod $wrappedMethod) { + $this->wrappers = $wrappedMethod->wrappers; + return $this; + } + + /** + * The wrappers and wrapped method are invoked in call(). + * First the before() methods of wrappers are invoked in the order + * in which the Wrappers were applied. The before methods are passed + * the arguments which will eventually be passed to the actual wrapped + * method by reference for possible manipulation. If a before method + * returns a value this value is short-circuits the calling process + * and returns that value immediately. Next the wrapped method is + * invoked. The result is then passed to the after() methods of wrappers + * in the reverse order in which they were applied. The after methods + * can manipulate the returned value. + * + * @return mixed + */ + function call() { + $args = func_get_args(); + + $object = array_shift($args); + + foreach($this->wrappers as $wrapper) { + $returns = $wrapper->before($object, $args); + if($returns !== null) { + // Short-circuit return + return $returns; + } + } + + if(!isset($this->reflectedMethod)) { + $this->reflectedMethod = new ReflectionMethod($object, $this->method); + } + + $returns = $this->reflectedMethod->invokeArgs($object, $args); + + foreach(array_reverse($this->wrappers) as $wrapper) { + $wrapperReturn = $wrapper->after($object, $returns); + if($wrapperReturn !== null) { + $returns = $wrapperReturn; + } + } + + return $returns; + } +} ?> \ No newline at end of file diff --git a/recess/recess/lang/exceptions/InvalidAnnotationValueException.class.php b/recess/recess/lang/exceptions/InvalidAnnotationValueException.class.php index 7d41598..7ef25ad 100644 --- a/recess/recess/lang/exceptions/InvalidAnnotationValueException.class.php +++ b/recess/recess/lang/exceptions/InvalidAnnotationValueException.class.php @@ -1,10 +1,11 @@ - - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -class InvalidAnnotationValueException extends RecessErrorException { } -?> \ No newline at end of file + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class InvalidAnnotationValueException extends ErrorException { } \ No newline at end of file diff --git a/recess/recess/lang/exceptions/UnknownAnnotationException.class.php b/recess/recess/lang/exceptions/UnknownAnnotationException.class.php index b6ea827..4f2bb28 100644 --- a/recess/recess/lang/exceptions/UnknownAnnotationException.class.php +++ b/recess/recess/lang/exceptions/UnknownAnnotationException.class.php @@ -1,10 +1,12 @@ - - * @copyright 2008, 2009 Kris Jordan - * @package Recess PHP Framework - * @license MIT - * @link http://www.recessframework.org/ - */ -class UnknownAnnotationException extends RecessErrorException { } + + * @copyright 2008, 2009 Kris Jordan + * @package Recess PHP Framework + * @license MIT + * @link http://www.recessframework.org/ + */ +class UnknownAnnotationException extends ErrorException { } ?> \ No newline at end of file