Annotations in Drupal 8

Annotations are PHP comments which hold metadata about your function or class. They do not directly affect program semantics as they are comment blocks. They are read and parsed at runtime by an annotation engine.

Annotations are already used in other PHP projects for various purposes. Symfony2 uses annotations for specifying routing rules. Doctrine uses them for adding ORM related metadata.Though handy in various situations, their utility is debated about a lot, like:

  1. How to actually differentiate between annotations and actual user comments?

  2. Why put business logic inside comment blocks. Shouldn't they be a part of core language semantics?

  3. Annotations blur the boundary between code and comments. If the developer misses an annotation(Remember, its not a program semantic). It might compile fine, but might not work as expected.

  4. Another closely related gripe is, annotations are hard to test and debug.

Most of the acquisitions are around the concept of annotations implemented as comments in PHP. There is however, a proposal to add it as a first class language feature. In the meantime, we are stuck to using comment based annotations.

Annotations are not all evil. They make it easier to inject behaviour without adding a lot of boilerplate.

Here is an example taken from stackoverflow which shows how annotations can cut a lot of boilerplate code.

Let's say we want to inject weapon object to a Soldier instance.

class Weapon {
    public function shoot() {
        print "... shooting ...";
    }
}

class Soldier {
    private $weapon;

    public function setWeapon($weapon) {
        $this->weapon = $weapon;
    }

    public function fight() {
        $this->weapon->shoot();
    }
}

If the DI is done by hand, then:

$weapon = new Weapon();

$soldier = new Soldier();
$soldier->setWeapon($weapon); 
$soldier->fight();

We could go a step further and decouple the DI and put it in an external file, like:

Soldier.php

$soldier = Container::getInstance('Soldier');
$soldier->fight(); // ! weapon is already injected

soldierconfig.xml

<class name="Soldier">
    <!-- call setWeapon, inject new Weapon instance -->
    <call method="setWeapon">
        <argument name="Weapon" />
    </call>
</class>

If we use annotations instead:

class Soldier {
    ...

    /**
     * @inject $weapon Weapon
     */
    public function setWeapon($weapon) {
        $this->weapon = $weapon;
    }

Annotations also give the additional advantage of having both code and metadata co-located. I think this is another reason why it was decided to use annotations and do away with external configuration files in Drupal 8(at least for plugins).

The Drupal part

Drupal 8 borrows annotations syntax from Doctrine. Drupal 7 had metadata tucked away in info hooks. This involves reading the whole module file into memory for every request. Annotations, on the other hand, are tokenized and parsed and don't incur as much memory requirements as in the hook based approach. Also, docblocks are cached by the opcode cache.

Syntax

Drupal annotations are nested key value pairs very similar to json dumps. There are some gotchas though. You MUST use double quotes for strings and no quotes at all for numbers. Lists are represented by curly brackets and booleans by TRUE and FALSE without quotes. Here's a code dump of annotations in action, from TextDefaultFormatter.php file in text module.

/**
 * Plugin implementation of the 'text_default' formatter.
 *
 * @FieldFormatter(
 *   id = "text_default",
 *   label = @Translation("Default"),
 *   field_types = {
 *     "text",
 *     "text_long",
 *     "text_with_summary",
 *   },
 *   quickedit = {
 *     "editor" = "plain_text"
 *   }
 * )
 */
class TextDefaultFormatter extends FormatterBase {
...

With so many conveniences, I hope annotations become a first class citizen of PHP pretty soon!