mirror of
https://github.com/sigmasternchen/php-doc-en
synced 2025-03-15 08:28:54 +00:00

I think the constant names are self-explanatory enough to not require additional information Closes GH-1168.
394 lines
11 KiB
XML
394 lines
11 KiB
XML
<?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>The following targets can be specified:</para>
|
|
|
|
<simplelist>
|
|
<member><constant>Attribute::TARGET_CLASS</constant></member>
|
|
<member><constant>Attribute::TARGET_FUNCTION</constant></member>
|
|
<member><constant>Attribute::TARGET_METHOD</constant></member>
|
|
<member><constant>Attribute::TARGET_PROPERTY</constant></member>
|
|
<member><constant>Attribute::TARGET_CLASS_CONSTANT</constant></member>
|
|
<member><constant>Attribute::TARGET_PARAMETER</constant></member>
|
|
<member><constant>Attribute::TARGET_ALL</constant></member>
|
|
</simplelist>
|
|
|
|
<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
|
|
-->
|