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

Annotations

Frank Kleine edited this page Feb 28, 2015 · 15 revisions

Annotations

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.

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.

/**
 * Class to demonstrate how to define annotations
 *
 * @MyAnnotation
 */
class Foo
{
    /**
     * an example property
     *
     * @var  string
     * @AnnotationWithValues(bar='dummy', baz=42, required=true)
     */
    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)
    {
        // some code here
    }
}

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, required=true) This annotation has two values with the parameters bar, baz and required. One is a string, the other is an integer, and the last a boolean.
  • @AnnotationWithOneValue("anotherDummy") This annotation has only a single string value.
  • @CastedAnnotation[MyAnnotation] A casted annotation can be used distinguish between a marker and a hint which concrete actions the annotation consuming class should do. The first name is the marker, and the name part in square brackets denotes the action to follow.
  • @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 ReflectionMethod::getAnnotation('ParamAnnotation'), this will result in a ReflectionException.

In contrast to other implementations it is not required to create a separate class for the annotation.

Parameter types

The parameters can be one of the following types:

  • string: must be enclosed in single or double quotes, possible ways are foo="bar" and bar='Foo'

  • null: foo=null sets the value of foo to null, it can be written as NULL as well.

  • integer and float: 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

  • stubbles\lang\reflect\ReflectionClass: It is also possible to specify a class name as a parameter value: clazz=example\more\MyExample.class

  • Constants: 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.

Get annotations

Up until release 5.2.x

To get information about annotated elements use the Stubbles reflection classes:

$refClass = new stubbles\lang\reflect\ReflectionClass('Foo');
if ($refClass->hasAnnotation('MyAnnotation')) {
    var_dump($refClass->annotation('MyAnnotation'));
}

$refProperty = $refClass->getProperty('bar');
if ($refProperty->hasAnnotation('AnnotationWithValues')) {
    var_dump($refProperty->annotation('AnnotationWithValues'));
}

$refProperty = $refClass->getProperty('baz');
if ($refProperty->hasAnnotation('AnnotationWithValues')) {
    var_dump($refProperty->annotation('AnnotationWithValues'));
}

$refMethod = $refClass->getMethod('aMethod');
if ($refMethod->hasAnnotation('CastedAnnotation')) {
    var_dump($refMethod->annotation('CastedAnnotation'));
}

$refParameters = $refMethod->getParameters();
if (refParameters[0]->hasAnnotation('ParamAnnotation')) {
    var_dump($refParameters[0]->annotation('ParamAnnotation'));
}

Since release 5.3.0

To get a list of all annotations of an annotatable element call the stubbles\lang\reflect\annotationsOf() function. It returns an instance of stubbles\lang\reflect\annotation\Annotations and supports the following invocations:

annotationsOf('my\ExampleClass`, `someMethod`); // returns annotations of this method
annotationsOf($exampleInstance, `someMethod`); // returns annotations of this method
annotationsOf('my\ExampleClass`); // returns annotations of this class
annotationsOf($exampleInstance); // returns annotations of this class
annotationsOf('my\examplefunction`); // returns annotations of this function
annotationsOf($reflectionParameter); // returns annotations of this parameter
annotationsOf($reflectionProperty); // returns annotations of this class property

As convienience function to retrieve annotations of a class' constructor the function stubbles\lang\reflect\constructorAnnotationsOf() can be used:

constructorAnnotationsOf('my\ExampleClass`);
constructorAnnotationsOf($exampleInstance);

Read values from annotations

As seen above annotations can have values. If we take the @AnnotationWithValues from the class above this is how you can access them:

$annotation = $refClass->getProperty('bar')->annotation('AnnotationWithValues');
echo $annotation->getBar(); // prints "dummy"
echo $annotation->bar; // prints "dummy"
echo $annotation->getValueByName('bar'); // prints "dummy"
if ($annotation->isRequired()) {
    echo 'Required!';
}

These are the possibilities:

  • Boolean values can be retrieved using the method is followed by their name.
  • Any other value types can be retrieved using the method get followed by their name, as property of the annotation class, or using the getValueByName() method. Please note that boolean values can also be retrieved using the get method syntax, but most times it just doesn't look that good: getRequired() has a different connotation than isRequired() from the point of view of the reader of your code.

In case the annotation has an unnamed value only it can be retrieved using $annotation->value or $annotation->getValue().

Using the method syntax with get has the advantage that a default value can be supplied which will be returned in case a value with this name is not set:

echo $annotation->getAwesomeness('Roland TB-303'); // prints "Roland TB-303"

The default value for the is method syntax is false.

If values are optional their existence can be checked: $annotation->hasValueByName('bar') returns true if a value with the name bar is set, and false otherwise.

Annotation cache

Applies to versions >= 3.0.0

As parsing annotations is quite expensive Stubbles will cache annotations once they are read. However, the cache by default works only on a per request base and is not persistent. In order to make the annotation cache persistent between different requests the application needs to provide an annotation cache persistence.

The simplest way to do so is to call stubbles\lang\persistAnnotationsInFile('/path/to/some/cachefile.cache') which will then use the file named in the argument to persist the annotation cache inbetween requests.

If a cache file doesn't suit your needs you can also provide a different persistence. To do so, you need to call stubbles\lang\persistAnnotations() and provide two functions, one which is able to read the data and return it, and another one which can receive data and store it. For a file cache implementation, this would look like this:

stubbles\lang\persistAnnotations(
        function() use($cacheFile)
        {
            if (file_exists($cacheFile)) {
                return unserialize(file_get_contents($cacheFile));
            }

            return [];
        },
        function(array $annotationData) use($cacheFile)
        {
            file_put_contents($cacheFile, serialize($annotationData));
        }
);

The first function must return the stored annotation data. If no such data is present it must return an empty array.

The second function must store passed annotation data.

Please note that if you use a persistent annotation cache that you need to be able to clear this cache, as changes in your code on annotations will not lead to an update of the cached data. This might lead to confusion on why a changed annotation will not be respected when the code is executed.

Applies to versions < 3.0.0

As parsing annotations is quite expensive Stubbles will cache annotations once they are read. This means if you change an annotation in your code it doesn't become effective immediately. You will have to clear the cache using vendor/bin/clearCache.

If you perform an update with Composer and you registered the scripts as defined in the Installation guide the cache will be cleared automatically.

Clone this wiki locally