Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add chapter on Attributes in language section.
Closes GH-254 Co-authored-by: Benjamin Eberlei <beberlei@php.net> git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@351884 c90b9560-bf6c-de11-be94-00142212c4b1
- Loading branch information
Showing
1 changed file
with
377 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,377 @@ | ||
<?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 allow to add structured, machine-readable metadata information | ||
on declarations in code: Classes, methods, functions, parameters, | ||
properties and 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>, | ||
we use an attribute that can be used instead. 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() | ||
{ | ||
@mkdir($this->targetDirectory); | ||
} | ||
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 = $reflection->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, attribute | ||
declaration are always enclosed with a starting | ||
<literal>#[</literal> and a corresponding ending | ||
<literal>]</literal>. Inside, one or many attributes are listed, | ||
seperated 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>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()); | ||
} | ||
} | ||
dumpAttributeData(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>Using target specification to restrict where attributes can be used</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>Simple Attribute Class</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_REPEATBLE 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 | ||
--> |