Our previous post covered the task of reading annotations from DocBlocks. It described how to get the name and the parameters (also called attributes) for an annotation defined like this:

We finished the last post by taking a look at the AnnotationParser where we filled the variables $annotationName and $parameters with some data. That’s all we have to know about the annotation. So what’s next?

Our extended reflection API will keep all the information about the annotations. Thus, the answer is obvious: For each annotation, there is an object to be created which will encapsulate all data read out from the annotation definition.

Annotation class

To start with, we create a class called Annotation which will be the abstract base class for all annotations.

Now we can implement our annotation:

The actual instantiation of the objects is done within the AnnotationParser:

Since we’ve got only the class name of the annotation (without the namespace), the AnnotationParser has to know how it can build the fully qualified class name, e.g.:

has to be mapped to Exar\Aop\Interceptor\MyAnnotation defined above. At the moment, AnnotationParser only supports Exar\Aop\Interceptor as annotation namespace, so it only will look/build classnames using this namespace.

The fully qualified annotation class name is stored in $className. Now we check if the desired class was already declared by checking get_declared_classes(). If not, it will explicitly be loaded by Exar\Autoloader. After that, we are ready to create the annotation object using the standard reflection API. Two things are needed for that:

  1. Annotation parameters (attributes)
  2. Reflection object of the annotated class

After calling the constructor of the Annotation class, we store the object in an array which is returned from readAnnotations method when all lines of the given DocBlock are processed. Note that AnnotationParser has no information about the context of the DocBlock it’s parsing. It just reads the DocBlock, matches the annotations and returns Annotation objects as an array.

We are able to read the simple annotations so far. Consider the following class which uses @MyAnnotation:

@Exar is necessary to let the Exar autoloader know that this class has to be loaded within an AOP-aware context.

The following piece of code shows how our extended reflection API looks like in action (go here for explanation on reflection classes):

In this manner, we also can annotate methods.

Well, the example above is a very simple one. There are some questions to be answered:

  1. What about annotation parameters?
  2. How can we make sure that an annotation is used in a proper way (e.g. only for a class but not for a method?)

Annotation parameters

The example above describes a very simple annotation. However, most of the annotations are more complex and use parameters. For example, if you want to protect an area in your application from unauthorized access, you could mark some action methods in your controller as secured using an annotation. This annotation could contain the role required to call the annotated action:

In this case, you have to store the parameter as a protected class variable within the annotation object, so you can use the stored information every time the annotated method is called:

The variable $role is set during instantiation of the annotation object, inside of the constructor method of the abstract class Annotation. That’s the reason why this variable has to be at least protected:

If an annotation parameter cannot be recognized (because it doesn’t exist or it is private), an error message is triggered. You can define as many annotation parameters as you like by adding class variables to the annotation class. Don’t forget to deactivate the property $value in your annotation class constructor (otherwise your annotation will be recognized as a single-value annotation):

Implementing annotation restrictions

Consider a class containing a method to authenticate users. Let’s name this method login. If you want to log every login attempt on your page, you will probably write an interceptor that logs some information on every method call. You also will annotate the login method like this:

As you see, there’s no sense to annotate the whole class with @Audit because only this method implements the login process. So how can we restrict the usage of @Audit? In other words, how can we define which targets we can annotate with a specific annotation?

For this purpose, we introduce @Target. By using @Target you can define where an annotation has to be placed. @Target is a single-value annotation which contains either a string value or an array of strings, e.g.:

  • @Target("class") // allows an annotation to be placed on a class
  • @Target("method") // allows an annotation to be placed on a method
  • @Target({"method", "property"}) // allows an annotation to be placed on a method or on a property

If your annotation class is not @Target annotated, it is not bound to a specific kind of target. That means, you can place it everywhere which is the same as @Target({"class", "method", "property"}).

Once @Target is set, the reflection target can be checked during instantiation of annotation objects (within Annotation):

Now we can set restrictions on our annotations, e.g. if @Audit is only allowed on methods:

You are free to implement your own restriction for your annotations by overriding method checkCreationConstraints which is also called during instantiation within the constructor method of the Annotation class.

Simple Annotation

Usually for all annotations there is an actual class to be invoked. However, in case no suitable class can be found, Exar will provide a fallback mechanism by using a SimpleAnnotation:

This makes sure that at runtime there will be an object that encapsulates annotation information anyway. In this way, it is possible to use @Exar or @Target without implementing these classes.

Visit the project homepage or fork Exar on GitHub to learn more about Exar’s extended Reflection API.

Continue with Part 4: Weaving Strategies