From 9caba962ed2edea02944e149817bf934928f448f Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 4 Dec 2020 23:15:43 +0000 Subject: [PATCH] Add chapter on Attributes in language section. Closes GH-254 Co-authored-by: Benjamin Eberlei git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@351884 c90b9560-bf6c-de11-be94-00142212c4b1 --- language/attributes.xml | 377 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100755 language/attributes.xml diff --git a/language/attributes.xml b/language/attributes.xml new file mode 100755 index 000000000000..ed0cf5c3f1fe --- /dev/null +++ b/language/attributes.xml @@ -0,0 +1,377 @@ + + + + Attributes + + Attributes overview + + + + 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 + Reflection + APIs. Attributes could therefore be thought of as a configuration + language embedded directly into code. + + + + 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. + + + + A simple example of attribute usage is to convert an interface + that has optional methods to use attributes. Lets assume an + ActionHandler + 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 ActionHandler to implement + a method setUp(), + we use an attribute that can be used instead. One benefit + of this approach is that we can use the attribute several times. + + + + Implementing optional methods of an interface with Attributes + +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); +]]> + + + + + + Attribute syntax + + + There are several parts to the attributes syntax. First, attribute + declaration are always enclosed with a starting + #[ and a corresponding ending + ]. Inside, one or many attributes are listed, + seperated by comma. The attribute name is an unqualified, qualified + or fully-qualified name as described in Using Namespaces Basics. + Arguments to the attribute are optional, but are enclosed in the usual parenthesis (). + Arguments to attributes can only be literal values or constant expressions. Both positional and + named arguments syntax can be used. + + + + 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. + + + + Attribute Syntax + + +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 +{ +} +]]> + + + + + + + Reading Attributes with the Reflection API + + + To access attributes from classes, methods, functions, parameters, properties and class constants, + the Reflection API provides the method getAttributes on each of the corresponding + Reflection objects. This method returns an array of ReflectionAttribute instances + that can be queried for attribute name, arguments and to instantiate an instance of the represented attribute. + + + + 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 newInstance, objects of the attribute class are instantiated and the correct matching of arguments + is validated, not earlier. + + + + Reading Attributes using Reflection API + + +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) +} +*/ + +]]> + + + + + 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. + + + + Reading Specific Attributes using Reflection API + + +getAttributes(MyAttribute::class); + + foreach ($attributes as $attribute) { + var_dump($attribute->getName()); + var_dump($attribute->getArguments()); + var_dump($attribute->newInstance()); + } +} + +dumpAttributeData(new ReflectionClass(Thing::class)); +]]> + + + + + + Declaring Attribute Classes + + + 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 #[Attribute] attribute declared + that can be imported from the global namespace with a use statement. + + + + Using target specification to restrict where attributes can be used + + + + + + + + To restrict the type of declaration an attribute can be assigned to, a bitmask can be passed as the first + argument to the #[Attribute] declaration. + + + + Simple Attribute Class + + + + + + + Declaring MyAttribute on another type will now throw an exception during + the call to ReflectionAttribute::newInstance + + + + + 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 #[Attribute] declaration. + + + + Using IS_REPEATBLE to allow attribute on a declaration multiple times + + + + + + + + + +