Permalink
Cannot retrieve contributors at this time
382 lines (311 sloc)
9.99 KB
<?xml version="1.0" encoding="utf-8"?> | |
<!-- $Revision$ --> | |
<chapter xml:id="language.attributes" xmlns="http://docbook.org/ns/docbook"> | |
<title>Attributes</title> | |
<sect1 xml:id="language.attributes.overview"> | |
<title>Attributes overview</title> | |
<?phpdoc print-version-for="attributes"?> | |
<para> | |
Attributes offer the ability to add structured, machine-readable metadata information | |
on declarations in code: Classes, methods, functions, parameters, | |
properties and class constants can be the target of an attribute. The metadata | |
defined by attributes can then be inspected at runtime using the | |
<link linkend="book.reflection">Reflection | |
APIs</link>. Attributes could therefore be thought of as a configuration | |
language embedded directly into code. | |
</para> | |
<para> | |
With attributes the generic implementation of a | |
feature and its concrete use in an application can be decoupled. In a way it is | |
comparable to interfaces and their implementations. But where | |
interfaces and implementations are about code, attributes are about | |
annotating extra information and configuration. Interfaces can | |
be implemented by classes, yet attributes can also be declared | |
on methods, functions, parameters, properties and class constants. | |
As such they are more flexible than interfaces. | |
</para> | |
<para> | |
A simple example of attribute usage is to convert an interface | |
that has optional methods to use attributes. Lets assume an | |
<literal>ActionHandler</literal> | |
interface representing an operation in an application, where some | |
implementations of an action handler require setup and others do not. Instead of requiring all classes | |
that implement <literal>ActionHandler</literal> to implement | |
a method <literal>setUp()</literal>, | |
an attribute can be used. One benefit | |
of this approach is that we can use the attribute several times. | |
</para> | |
<example> | |
<title>Implementing optional methods of an interface with Attributes</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
interface ActionHandler | |
{ | |
public function execute(); | |
} | |
#[Attribute] | |
class SetUp {} | |
class CopyFile implements ActionHandler | |
{ | |
public string $fileName; | |
public string $targetDirectory; | |
#[SetUp] | |
public function fileExists() | |
{ | |
if (!file_exists($this->fileName)) { | |
throw new RuntimeException("File does not exist"); | |
} | |
} | |
#[SetUp] | |
public function targetDirectoryExists() | |
{ | |
if (!file_exists($this->targetDirectory)) { | |
mkdir($this->targetDirectory); | |
} elseif (!is_dir($this->targetDirectory)) { | |
throw new RuntimeException("Target directory $this->targetDirectory is not a directory"); | |
} | |
} | |
public function execute() | |
{ | |
copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName)); | |
} | |
} | |
function executeAction(ActionHandler $actionHandler) | |
{ | |
$reflection = new ReflectionObject($actionHandler); | |
foreach ($reflection->getMethods() as $method) { | |
$attributes = $method->getAttributes(SetUp::class); | |
if (count($attributes) > 0) { | |
$methodName = $method->getName(); | |
$actionHandler->$methodName(); | |
} | |
} | |
$actionHandler->execute(); | |
} | |
$copyAction = new CopyFile(); | |
$copyAction->fileName = "/tmp/foo.jpg"; | |
$copyAction->targetDirectory = "/home/user"; | |
executeAction($copyAction); | |
]]> | |
</programlisting> | |
</example> | |
</sect1> | |
<sect1 xml:id="language.attributes.syntax"> | |
<title>Attribute syntax</title> | |
<para> | |
There are several parts to the attributes syntax. First, an attribute | |
declaration is always enclosed with a starting | |
<literal>#[</literal> and a corresponding ending | |
<literal>]</literal>. Inside, one or many attributes are listed, | |
separated by comma. The attribute name is an unqualified, qualified | |
or fully-qualified name as described in <link linkend="language.namespaces.basics">Using Namespaces Basics</link>. | |
Arguments to the attribute are optional, but are enclosed in the usual parenthesis <literal>()</literal>. | |
Arguments to attributes can only be literal values or constant expressions. Both positional and | |
named arguments syntax can be used. | |
</para> | |
<para> | |
Attribute names and their arguments are resolved to a class and the arguments are passed to its constructor, | |
when an instance of the attribute is requested through the Reflection API. As such | |
a class should be introduced for each attribute. | |
</para> | |
<example> | |
<title>Attribute Syntax</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
// a.php | |
namespace MyExample; | |
use Attribute; | |
#[Attribute] | |
class MyAttribute | |
{ | |
const VALUE = 'value'; | |
private $value; | |
public function __construct($value = null) | |
{ | |
$this->value = $value; | |
} | |
} | |
// b.php | |
namespace Another; | |
use MyExample\MyAttribute; | |
#[MyAttribute] | |
#[\MyExample\MyAttribute] | |
#[MyAttribute(1234)] | |
#[MyAttribute(value: 1234)] | |
#[MyAttribute(MyAttribute::VALUE)] | |
#[MyAttribute(array("key" => "value"))] | |
#[MyAttribute(100 + 200)] | |
class Thing | |
{ | |
} | |
#[MyAttribute(1234), MyAttribute(5678)] | |
class AnotherThing | |
{ | |
} | |
]]> | |
</programlisting> | |
</example> | |
</sect1> | |
<sect1 xml:id="language.attributes.reflection"> | |
<title>Reading Attributes with the Reflection API</title> | |
<para> | |
To access attributes from classes, methods, functions, parameters, properties and class constants, | |
the Reflection API provides the method <function>getAttributes</function> on each of the corresponding | |
Reflection objects. This method returns an array of <classname>ReflectionAttribute</classname> instances | |
that can be queried for attribute name, arguments and to instantiate an instance of the represented attribute. | |
</para> | |
<para> | |
This separation of reflected attribute representation from actual instance increases control of the programmer | |
to handle errors regarding missing attribute classes, mistyped or missing arguments. Only after | |
calling <function>ReflectionAttribute::newInstance</function>, objects of the attribute class are instantiated and the correct matching of arguments | |
is validated, not earlier. | |
</para> | |
<example> | |
<title>Reading Attributes using Reflection API</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
#[Attribute] | |
class MyAttribute | |
{ | |
public $value; | |
public function __construct($value) | |
{ | |
$this->value = $value; | |
} | |
} | |
#[MyAttribute(value: 1234)] | |
class Thing | |
{ | |
} | |
function dumpAttributeData($reflection) { | |
$attributes = $reflection->getAttributes(); | |
foreach ($attributes as $attribute) { | |
var_dump($attribute->getName()); | |
var_dump($attribute->getArguments()); | |
var_dump($attribute->newInstance()); | |
} | |
} | |
dumpAttributeData(new ReflectionClass(Thing::class)); | |
/* | |
string(11) "MyAttribute" | |
array(1) { | |
["value"]=> | |
int(1234) | |
} | |
object(MyAttribute)#3 (1) { | |
["value"]=> | |
int(1234) | |
} | |
*/ | |
]]> | |
</programlisting> | |
</example> | |
<para> | |
Instead of iterating all attributes on the reflection instance, only those | |
of a particular attribute class can be | |
retrieved by passing the searched attribute class name as argument. | |
</para> | |
<example> | |
<title>Reading Specific Attributes using Reflection API</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
function dumpMyAttributeData($reflection) { | |
$attributes = $reflection->getAttributes(MyAttribute::class); | |
foreach ($attributes as $attribute) { | |
var_dump($attribute->getName()); | |
var_dump($attribute->getArguments()); | |
var_dump($attribute->newInstance()); | |
} | |
} | |
dumpMyAttributeData(new ReflectionClass(Thing::class)); | |
]]> | |
</programlisting> | |
</example> | |
</sect1> | |
<sect1 xml:id="language.attributes.classes"> | |
<title>Declaring Attribute Classes</title> | |
<para> | |
While not strictly required it is recommended to create an actual class for every attribute. | |
In the most simple case only an empty class is needed with the <literal>#[Attribute]</literal> attribute declared | |
that can be imported from the global namespace with a use statement. | |
</para> | |
<example> | |
<title>Simple Attribute Class</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
namespace Example; | |
use Attribute; | |
#[Attribute] | |
class MyAttribute | |
{ | |
} | |
]]> | |
</programlisting> | |
</example> | |
<para> | |
To restrict the type of declaration an attribute can be assigned to, a bitmask can be passed as the first | |
argument to the <literal>#[Attribute]</literal> declaration. | |
</para> | |
<example> | |
<title>Using target specification to restrict where attributes can be used</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
namespace Example; | |
use Attribute; | |
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] | |
class MyAttribute | |
{ | |
} | |
]]> | |
</programlisting> | |
<para> | |
Declaring <classname>MyAttribute</classname> on another type will now throw an exception during | |
the call to <function>ReflectionAttribute::newInstance</function> | |
</para> | |
</example> | |
<para> | |
By default an attribute can only be used once per declaration. If the attribute should be repeatable on declarations it must | |
be specified as part of the bitmask to the <literal>#[Attribute]</literal> declaration. | |
</para> | |
<example> | |
<title>Using IS_REPEATABLE to allow attribute on a declaration multiple times</title> | |
<programlisting role="php"> | |
<![CDATA[ | |
<?php | |
namespace Example; | |
use Attribute; | |
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] | |
class MyAttribute | |
{ | |
} | |
]]> | |
</programlisting> | |
</example> | |
</sect1> | |
</chapter> | |
<!-- Keep this comment at the end of the file | |
Local variables: | |
mode: sgml | |
sgml-omittag:t | |
sgml-shorttag:t | |
sgml-minimize-attributes:nil | |
sgml-always-quote-attributes:t | |
sgml-indent-step:1 | |
sgml-indent-data:t | |
indent-tabs-mode:nil | |
sgml-parent-document:nil | |
sgml-default-dtd-file:"~/.phpdoc/manual.ced" | |
sgml-exposed-tags:nil | |
sgml-local-catalogs:nil | |
sgml-local-ecat-files:nil | |
End: | |
vim600: syn=xml fen fdm=syntax fdl=2 si | |
vim: et tw=78 syn=sgml | |
vi: ts=1 sw=1 | |
--> |