Annotations can be introduced by extending the abstract class recess.lang.Annotation. User defined annotation classes must end in the word 'Annotation'. For example: to have a !Protected annotation the classname must be ProtectedAnnotation. There are four abstract methods which must be implemented by custom annotations: usage, isFor, validate, and expand. These methods are used by the framework to make working with user-defined annotations feel like a first-class PHP construct. Following are descriptions of the purpose for each annotation:

usage - Returns a string representation of the intended usage of an annotation.

isFor - 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.


validate($class) - 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.

expand($class, $reflection, $descriptor) - The expansion step of an annotation gives it an opportunity to manipulate a class' descriptor by introducing additional metadata, attach methods, and wrap methods. Parameters: $class the classname the annotation is applied to. $reflection The PHP Reflection(Class|Method|Property) object the annotation is located on. $descriptor is the ClassDescriptor that the annotation is being expanded on.

The power of annotations comes in the expand step. An annotation can modify a class' descriptor which allows for methods to be attached to a class and available on all instances of a class, as well as "wrap" methods of a class marked as !Wrappable

To demonstrate authoring a custom annotation, let's create an annotation for methods on a Controller that should be protected by a protected cookie key of "secret" and value of "password". Our goal is to be able to place !CookieProtected on a method in a controller and redirect users to an optionally specified path (else, "/") if they do not have the necessary cookie. We'll craft the annotation in two-steps, first we'll implement the abstract methods of Annotation, then we'll take combine the annotation with the previously discussed WrappedMethods to finish off the functionality of the CookieProtectedAnnotation by wrapping a Controller's serve method.


The full list of helper methods for validate is: acceptedKeys, requiredKeys, acceptedKeylessValues, acceptedIndexedValues, acceptedValuesForKey, acceptsNoKeylessValues, acceptsNoKeyedValues, validOnSubclassesOf, minimumParameterCount, maximumParameterCount, exactParameterCount. To understand the meaning of 'keys', 'indexed values', 'keyless values', you must understand the way recess interprets annotations. Recess parses an annotation by doing the following: 1) find an annotation by looking for an !Bang followed by whitespace, "!Bang". 2) Convert everything after the start of an annotation into a parameters array, !Bang foo, key: bar becomes array("foo", "key" => "bar"). 3) Instantiate the class named BangAnnotation and pass the parameters into the BangAnnotationinstance's init method. Thus, every subclass of Annotation has a $parameters property which is this array. The helper methods provide convenient ways of checking the contents of this array during validate.

Once validate has been run, if errors are found in the annotation's $errors property a RecessErrorException is thrown. If no errors are found, then the final step before expand is called is to make all keyed values in the $parameters array properties of the annotation instance and to append all keyless values of $parameters to the $values property. For example, the annotation !Bang foo, Key: bar becomes: $bang = new BangAnnotation(); $bang->init(array('foo', 'Key' => 'bar')); Finally, after Recess calls expandAnnotation (which, in turn, calls your user-defined expand), your expand can reference $this->values[0] to find 'foo' and $this->key to get 'bar'.