Skip to content

Commit

Permalink
Documentation in additional places in recess/lang.
Browse files Browse the repository at this point in the history
  • Loading branch information
KrisJordan committed Sep 7, 2009
1 parent eaf4ca0 commit 176d6b4
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 30 deletions.
92 changes: 71 additions & 21 deletions recess/recess/lang/Annotation.class.php
Expand Up @@ -2,7 +2,8 @@
namespace recess\lang;

/**
* Base class for class, method, and property annotations.
* Base class for class, method, and property Recess Annotations.
* New Annotations can be introduced by extending this abstract class.
*
* @author Kris Jordan <krisjordan@gmail.com>
* @copyright 2008, 2009 Kris Jordan
Expand All @@ -12,20 +13,13 @@
*/
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();

public $parameters = array();

static protected $registeredAnnotations = array();

const FOR_CLASS = 1;
const FOR_METHOD = 2;
const FOR_PROPERTY = 4;
Expand Down Expand Up @@ -81,37 +75,48 @@ abstract protected function expand($class, $reflection, $descriptor);
/* Begin validation helper methods */

protected function acceptedKeys($keys) {
$passed = true;
foreach($this->parameters as $key => $value) {
if (is_string($key) && !in_array($key, $keys)) {
$this->errors[] = "Invalid parameter: \"$key\".";
$passed = false;
}
}
return $passed;
}

protected function requiredKeys($keys) {
$passed = true;
foreach($keys as $key) {
if(!array_key_exists($key, $this->parameters)) {
$this->errors[] = get_class($this) . " requires a '$key' parameter.";
$passed = false;
}
}
return $passed;
}

protected function acceptedKeylessValues($values) {
$passed = true;
foreach($this->parameters as $key => $value) {
if(!is_string($key) && !in_array($value, $values)) {
$this->errors[] = "Unknown parameter: \"$value\".";
$passed = false;
}
}
return $passed;
}

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) . '.';
return false;
}
return true;
}

protected function acceptedValuesForKey($key, $values, $case = null) {
if(!isset($this->parameters[$key])) { return; }
if(!isset($this->parameters[$key])) { return true; }

if($case === null) {
$value = $this->parameters[$key];
Expand All @@ -120,45 +125,62 @@ protected function acceptedValuesForKey($key, $values, $case = null) {
} 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) . '.';
return false;
}

return true;
}

protected function acceptsNoKeylessValues() {
$this->acceptedKeylessValues(array());
return $this->acceptedKeylessValues(array());
}

protected function acceptsNoKeyedValues() {
$this->acceptedKeys(array());
return $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.";
return false;
}
return true;
}

protected function minimumParameterCount($count) {
if( ! (count($this->parameters) >= $count) ) {
$this->errors[] = get_class($this) . " takes at least $count parameters.";
return false;
}
return true;
}

protected function maximumParameterCount($count) {
if( ! (count($this->parameters) <= $count) ) {
$this->errors[] = get_class($this) . " takes at most $count parameters.";
return false;
}
return true;
}

protected function exactParameterCount($count) {
if ( count($this->parameters) != $count ) {
$this->errors[] = get_class($this) . " requires exactly $count parameters.";
return false;
}
return true;
}

/* End validation helper methods */

/**
* Is a value in the array of values?
*
* @param varies $value
* @return bool
*/
function isAValue($value) {
return in_array($value, $this->values);
}
Expand All @@ -178,7 +200,18 @@ function valueNotIn($values) {
return null;
}


/**
* The strategy for realizing a Recess Annotation is driven
* by this method. The user experience of diagnostics is defined
* here, as well, by checking the correctness of an Annotation
* and giving annotation implementors a mechanism for providing
* feedback on the correct usage of an annotation.
*
* @param string $class The classname this annotation appears on.
* @param varies $reflection The Reflection object the annotation corresponds to.
* @param ClassDescriptor $descriptor the class descriptor.
* @return ClassDescriptor
*/
function expandAnnotation($class, $reflection, $descriptor) {
// First check to ensure this annotation is allowed
// to apply to this type of PHP construct (class, method, property)
Expand Down Expand Up @@ -211,9 +244,9 @@ function expandAnnotation($class, $reflection, $descriptor) {

// Throw Exception if Annotation Errors Exist
if(!empty($this->errors)) {
if($reflection instanceof ReflectionProperty) {
if($reflection instanceof \ReflectionProperty) {
$message = 'Invalid ' . get_class($this) . ' on property "' . $reflection->getName() . '". ';
$reflection = new ReflectionClass($class);
$reflection = new \ReflectionClass($class);
} else {
$message = 'Invalid ' . get_class($this) . ' on ' . $annotationIsOnType . ' "' . $reflection->getName() . '". ';
}
Expand All @@ -222,7 +255,7 @@ function expandAnnotation($class, $reflection, $descriptor) {
}
$message .= "\n == Errors == \n * ";
$message .= implode("\n * ", $this->errors);
throw new ErrorException($message,0,0,$reflection->getFileName(),$reflection->getStartLine(),array());
throw new \ErrorException($message,0,0,$reflection->getFileName(),$reflection->getStartLine(),array());
}

// Map keyed parameters to properties on this annotation
Expand All @@ -242,6 +275,8 @@ function expandAnnotation($class, $reflection, $descriptor) {
// Annotation developers can implement glorious new
// functionalities.
$this->expand($class, $reflection, $descriptor);

return $descriptor;
}

/**
Expand All @@ -253,9 +288,24 @@ function init($parameters) {
}

/**
* Given a docstring, returns an array of Recess Annotations.
* @param $docstring
* @return unknown_type
* To register an annotation this static method must be called.
* This allows annotations to use a flat namespace even when the
* annotations themselves are distributed across many different
* namespaces.
*/
static public function load() {
$fullClass = get_called_class();
$class = explode('\\',$fullClass);
self::$registeredAnnotations[end($class)] = $fullClass;
}

/**
* Given a docstring, returns an array of Recess Annotations. Throws
* an exception if the docstring cannot be parsed or if an annotation
* has not been loaded yet.
*
* @param $docstring String.
* @return array of Annotations
*/
static function parse($docstring) {
preg_match_all('%(?:\s|\*)*!(\S+)[^\n\r\S]*(?:(.*?)(?:\*/)|(.*))%', $docstring, $result, PREG_PATTERN_ORDER);
Expand Down
42 changes: 37 additions & 5 deletions recess/recess/lang/AttachedMethod.class.php
Expand Up @@ -2,23 +2,48 @@
namespace recess\lang;

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

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

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

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

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

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

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

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

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

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

Expand Down
2 changes: 1 addition & 1 deletion recess/test/recess/lang/ReflectionMethodTest.php
Expand Up @@ -6,7 +6,7 @@
use made\up\space\DummyAnnotation;

class ReflectionMethodTest extends PHPUnit_Framework_TestCase {

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

0 comments on commit 176d6b4

Please sign in to comment.