Press "Enter" to skip to content

New in PHP 8: Attributes (Annotations)

PHP 8's alpha drops in a few days, packed with new features. The one I personally care about most is of course the JIT — the thing I've been working on since 2013 through countless setbacks and failures, finally about to ship.

But today I'm not going to talk about the JIT. Once PHP 8 is out, I'll write a proper "Deep Dive into PHP 8's JIT" series for that.

Today I want to talk about attributes. Why? Yesterday I saw an article making the rounds in group chats, "Understanding Attributes in PHP 8". Honestly, it reads like a straight translation from English — dense and obscure. Plenty of people finished it more confused than when they started.

So let me spend one post explaining what this thing actually is, in plain terms.

Update (2020-09-07): the latest RFC vote passed, changing the attribute syntax from <<>> to #[]. This post has been updated to use the new syntax.

Before attributes, let's talk about the comments we've always had. In PHP projects you constantly see things like these @param and @see tags:

/**
 * @param Foo $argument
 * @see https:/xxxxxxxx/xxxx/xxx.html
 */
 function dummy($Foo) {}

These are doc comments. To PHP itself, the @param and @see inside are meaningless — the whole block is just stored as a string called doc_comment attached to the function/method.

If you want to interpret what the comment means, you have to invent your own conventions — like the @+name style above — and then parse the string yourself to extract the information.

For example, to pull out the @see tag, you'd end up writing something like:

$ref = new ReflectionFunction("dummy");
$doc = $ref->getDocComment();
$see = substr($doc, strpos($doc, "@see") + strlen("@see "));

String surgery like this is fiddly and error-prone.

Attributes, then, are simply an upgrade from "comments" to structured "annotations":

Take the same example:

#[Params("Foo", "argument")]
#[See("https://xxxxxxxx/xxxx/xxx.html")]
function dummy($argument) {}

With multiple attributes you can also write:

#[
 Params("Foo", "argument"),
 See("https://xxxxxxxx/xxxx/xxx.html")
]
function dummy($argument) {}

Don't get hung up on why you'd write it this way — functionally, you can now retrieve these structured annotations through Reflection. Say we want the See attribute:

$ref = new ReflectionFunction("dummy");

var_dump($ref->getAttributes("See")[0]->getName());
var_dump($ref->getAttributes("See")[0]->getArguments());

This outputs:

string(3) "See"
array(1) {
  [0]=>
  string(30) "https://xxxxxxxx/xxxx/xxx.html"
}

There's also a more advanced move: defining what's called an "attribute class":

<?php
#[Attribute(Attribute::TARGET_FUNCTION)]
class MyAttribute {
	public function __construct($name, $value) {
		var_dump($name);
		var_dump($value);
	}
}

Then you can write — note the newInstance call:

#[MyAttribute("See", "https://xxxxxxxx/xxxx/xxx.html")]
function dummy($argument) {
}
$ref = new ReflectionFunction("dummy");

$ref->getAttributes("MyAttribute")[0]->newInstance();

Run this and you'll see MyAttribute's __construct being called, with "See" and "https://xxx" passed as arguments.

Get it? You can "instantiate" an attribute — and on top of that ability you can build your own "comments as configuration" designs.

To sum up, attributes take these forms:

#[Name]
#[Name(Arguments)]
#[Name(Argunment1, Arguments2, ArgumentN)]

#[Name1(Argument), Name2(Argument), Name3(Argument)]

You then fetch an attribute by name through PHP's Reflection API with getAttributes("Name"); from the returned objects, getName() gives you the name and getArguments() the arguments in the parentheses.

Going one step further: if Name is an attribute class you defined yourself — marked with #[Attribute(Attribute::TARGET_FUNCTION)], or one of:

TARGET_CLASS    // attribute class for classes
TARGET_FUNCTION // attribute class for functions
TARGET_METHOD   // attribute class for methods
TARGET_PROPERTY // attribute class for properties
TARGET_CLASS_CONSTANT // attribute class for class constants
TARGET_PARAMETER // attribute class for parameters
TARGET_ALL

declaring what kind of target the attribute applies to — then you can call newInstance(), which is effectively "new Name(Arguments)".

At this point a lot of people will ask: what's the actual use?

Honestly, I'm usually indifferent to new language features. But this Attributes one — it's got to be worth at least a little something. 🙂

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.