Skip to content
This repository has been archived by the owner on Jan 16, 2019. It is now read-only.

Docs Reflection Annotations

Frank Kleine edited this page Apr 7, 2012 · 1 revision

Table of Contents

Annotations in Stubbles

This page describes how annotations can be used in Stubbles. For details about the concept behind annotations see annotations which contains a general introduction into the topic. For more details how to use this in a programming language see Java annotations.

See Extended Reflection API for how to obtain informations about annotations on classes, methods, functions and properties.

How to define an annotation

Annotations can be defined on every element that can get a docblock comment: functions, methods, classes and properties. Additionally it is possible to define annotations for parameters in the docblock comment of the function or method where the parameter is part of.

#php
<?php
/**
*Class to demonstrate how to define annotations
 *
*@author      Frank Kleine
*@package     stubbles
*@subpackage  examples
*@MyAnnotation
*/
class Foo
{
    /**
***an example property
     *
***@var  string
***@AnnotationWithValues(bar='dummy', baz=42)
***/
    protected $bar;

    /**
***another example property
     *
***@var  string
***@AnnotationWithOneValue("anotherDummy")
***/
    protected $baz;

    /**
***an example method
     *
***@param  int  $param  a parameter
***@CastedAnnotation[MyAnnotation]()
***@ParamAnnotation{param}(key='value')
***/
    public function aMethod($param)
    {
        /* imagine lots of code here, but not more than one screen page */
    }
}
?>

In the above example you can see five different ways of defining an annotation. However, you can combine these ways as you like. You may have a casted annotation with no, one or more values. But lets go through all defined annotations.

@MyAnnotation This is an annotation without any value.

@AnnotationWithValues(bar='dummy', baz=42) This annotation has two values with the parameters bar and baz. One is a string, the other is an integer.

@AnnotationWithOneValue("anotherDummy") This annotation has only a single string value.

@CastedAnnotation[MyAnnotation]() A casted annotation can be used, if you want your annotations not only to contain data, but also operations. The application accessing the annotation will request it using CastedAnnotation, but it will receive an instance of MyAnnotation. Please note, that MyAnnotation must be of the same type as CastedAnnotation, i.e. it must extend CastedAnnotation.

@ParamAnnotation{param}(key='value') This annotation is a parameter annotation defined for the parameter $param of the method aMethod(). The name in the curly braces denotes the name of the parameter this annotation is for. Please be aware that this annotation is not available when retrieving it via stubReflectionMethod::getAnnotation('ParamAnnotation'), this will result in a ReflectionException.

This example requires that four classes have to be defined: MyAnnotation, AnnotationWithValues, AnnotationWithOneValue and ParamAnnotation. Furthermore you need to define a class or interface called CastedAnnotation. Every annotation has to implement the [source:trunk/src/main/php/net/stubbles/reflection/annotations/stubAnnotation.php] provided by the extended reflection API. To make simple things simple the [source:trunk/src/main/php/net/stubbles/reflection/annotations/stubAbstractAnnotation.php] provides an abstract implementation which does some of the work the interface requires.

These classes will probably look like this:

#php
<?php
interface CastedAnnotation {
}
class MyAnnotation extends stubAbstractAnnotation implements stubAnnotation, CastedAnnotation
{
    public function getAnnotationTarget()
    {
        return stubAnnotation::TARGET_ALL;
    }
}
class AnnotationWithValues extends stubAbstractAnnotation implements stubAnnotation
{
    protected $bar;
    public $baz;

    public function getAnnotationTarget()
    {
        return stubAnnotation::TARGET_METHOD + stubAnnotation::TARGET_PROPERTY;
    }

    public function setBar($bar) { $this->bar = $bar; }

    public function getBar() { return $this->bar; }
}
class AnnotationWithOneValue extends stubAbstractAnnotation implements stubAnnotation
{
    protected $value;

    public function getAnnotationTarget()
    {
        return stubAnnotation::TARGET_PROPERTY;
    }

    public function setValue($value) { $this->value = $value; }
}
class ParamAnnotation extends stubAbstractAnnotation implements stubAnnotation
{
    protected $key;

    public function getAnnotationTarget()
    {
        return stubAnnotation::TARGET_PARAM;
    }

    public function setKey($value) { $this->key= $value; }
}
?>

For an explanation of the return values of the getAnnotationTarget() please see further below.

The above example shows you how to write the annotation classes: the first one, MyAnnotation, doesn't have any parameters. The second, AnnotationWithValues, has two parameters. The one named bar will be set via the setBar() method, the second named baz will be set directly into the property because it is defined as public. You may even not define the property nor the setter method if your annotation class provides a __set() method. However the calling precedence is as following:

  1. The setter method. If the parameter is called bar the setter method has to be called setBar($bar). The method has to be defined as public and can not be static.
  2. Public properties. If no setBar($bar) method is defined the annotation factory will look out if a public property $bar exists. This can not be static.
  3. If no setter method exists and no such property is defined the annotation factory will check if a magic __set() method is defined. It will be called with __set('bar', 'value of bar').

If none of these methods work a ReflectionException will be thrown when you try to get an instance of the annotation.

The third example AnnotationWithOneValue only provides a setValue($value) method. This is sufficient if your annotation requires only one parameter - there is no need to have an explicit name for it.

If you are afraid of naming clashes, you should prefix your annotation classes with stub and postfix them with Annotation. The reflection API will still find these classes. This also allows you to get rid of Annotation in the annotation name in the docblock, while the class still will contain Annotation in its name.

Validating annotations

To make sure, that your annotation received all required arguments, you can implement a finish() method in your annotation, that will be called, after all arguments have been set.

If a parameter is missing or has been initialized with an invalid value, you may throw a ReflectionException.

Parameter types

The parameters can be one of the following types:

type Example
string Strings must be enclosed in single or double quotes, possible params are foo="bar", bar='Foo'
null foo=null sets the value of foo to null, it can be written as NULL as well.
integer and float Integers are written like in PHP: foo=42, bar=1.34
boolean If you assign true or false to a paramter without enclosing it in quotes, it will be converted to a boolean value: foo=true,bar=false
stubReflectionClass It is also possible to specify a class name as a parameter value: clazz=net::stubbles::xml::stubDomXmlStreamWriter.class
Constants To get the value of a constant simply use foo=BAR_CONST. If BAR_CONST has been defined earlier as a constant than the result value of foo will be the value of the constant. If no constant is found the result value of foo will be BAR_CONST.

Using annotations

Now we want to make use of the defined annotations:

#php
<?php
$refClass = new stubReflectionClass('Foo');
if ($refClass->hasAnnotation('MyAnnotation') == true) {
    var_dump($refClass->getAnnotation('MyAnnotation'));
}
$refProperty = $refClass->getProperty('bar');  // $refProperty is an instance of stubReflectionProperty
if ($refProperty ->hasAnnotation('AnnotationWithValues') == true) {
    var_dump($refProperty ->getAnnotation('AnnotationWithValues'));
}
$refProperty = $refClass->getProperty('baz');
if ($refProperty ->hasAnnotation('AnnotationWithValues') == true) {
    var_dump($refProperty ->getAnnotation('AnnotationWithValues'));
}
$refMethod = $refClass->getMethod('aMethod');    // $refMethod is an instance of stubReflectionMethod
if ($refMethod ->hasAnnotation('CastedAnnotation') == true) {
    var_dump($refMethod ->getAnnotation('CastedAnnotation'));
}
$refParameters = $refMethod->getParameters();
if (refParameters[0]->hasAnnotation('ParamAnnotation') == true) {
    var_dump($refParameters[0]->getAnnotation('ParamAnnotation'));
}
?>

This will output something like:

object(MyAnnotation)[1]

object(AnnotationWithValues)[2]
  protected 'bar' => 'dummy' (length=5)
  public 'baz' => 42

object(AnnotationWithOneValue)[3]
  protected 'value' => 'anotherDummy' (length=12)

object(MyAnnotation)[4]

object(ParamAnnotation)[5]
  protected 'key' => 'value' (length=5)

Restricting annotations

You may wonder why the {{net::stubbles::reflection::annotations::stubAnnotation interface demands a getAnnotationTarget() method. With this method you are able to restrict the use of your annotations to classes or methods or functions or properties or any combination of them. For our example above this means that MyAnnotation can be used for any of them, while AnnotationWithValues is restricted to methods and properties and AnnotationWithOneValue is even more restrictive as it can only be applied on properties while ParamAnnotation can only be applied to function or method parameters. If you use an annotation on an invalid target, e.g. an annotation which is restricted to classes within a docblock of a method, the net::stubbles::reflection::stubReflection*::hasAnnotation() method will return false, and net::stubbles::reflection::stubReflection*::getAnnotation() will throw a ReflectionException if you try to get this annotation from the method in question.

Annotations in Stubbles

To learn more about annotations, you might want to take a look at the following packages in Stubbles:

  1. net::stubbles::xml::serializer::stubXMLSerializer uses several annotations that influence the way an object is serialized into XML
  2. net::stubbles::service::jsonrpc::stubJsonRpcProcessor exposes only those methods as an AJAX service that are marked as a WebMethod.

For more examples of annotations in Stubbles, take a look at the list of available annotations in Stubbles.

Further reading

To learn more about annotations in Stubbles, you might want to take a look at the presentation "Declarative Development Using Annotations in PHP" we gave at the PHP Conference 2007 Spring Edition in Ludwigsburg.

Clone this wiki locally