Files
php-doc-ru/language/attributes.xml

408 lines
15 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 9155d793178b5fcd5131b734cd174fecc34c1ae6 Maintainer: rjhdby Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="language.attributes" xmlns="http://docbook.org/ns/docbook">
<title>Атрибуты</title>
<sect1 xml:id="language.attributes.overview">
<title>Введение в атрибуты</title>
<?phpdoc print-version-for="attributes"?>
<para>
Атрибуты помогают добавлять к объявлениям в коде структурированную машиночитаемую метаинформацию.
Атрибуты нацеливают на классы, включая анонимные, методы, функции, параметры, свойства и константы класса.
Затем во время выполнения кода метаданные, которые определили атрибутами,
инспектируют <link linkend="book.reflection">через API-интерфейс модуля Reflection</link>.
Поэтому атрибуты рассматривают как язык конфигурации, который встраивается в код.
</para>
<para>
Атрибуты разделяют общее и специфическое поведение классов,
параметров, свойств и других элементов кода в приложении, на которые нацелили атрибут.
В каком-то смысле атрибуты похожи на интерфейс с его реализациями.
Но интерфейсы и реализации — это про код, а атрибуты — про добавление
дополнительной информации и конфигурацию. Интерфейсы реализуют только классами,
тогда как атрибуты разрешается нацеливать на методы, функции, параметры, свойства
и константы классов. Поэтому атрибуты — существенно более гибкий механизм, чем интерфейсы.
</para>
<para>
Простой пример замены интерфейса с необязательными методами на код с атрибутами.
Предположим, интерфейс <literal>ActionHandler</literal> описывает
в приложении операцию, для выполнения которой одним реализациям требуется
предварительная настройка, а другим — нет. И вместо внесения
в интерфейс <literal>ActionHandler</literal> дополнительного метода
<literal>setUp()</literal>, который для части реализаций останется пустым,
указывают атрибут. Отдельное преимущество внедрения в код атрибутов состоит в том,
что атрибут разрешается нацеливать много раз.
</para>
<example>
<title>Пример реализации опциональных методов интерфейса атрибутами</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>Синтаксис атрибутов</title>
<para>
Синтаксис атрибутов состоит из нескольких частей. Во-первых, декларация
атрибута начинается с символов <literal>#[</literal> и заканчивается
символом <literal>]</literal>. Во-вторых, внутри конструкции перечисляют
один или больше атрибутов, которые разделяют запятой.
Названия атрибутов по аналогии с названиями классов указывают неполными,
полными и абсолютными, как описывает раздел
«<link linkend="language.namespaces.basics">Основы пространств имён</link>».
Аргументы атрибутов необязательны, но если аргументы указали,
их заключают в круглые скобки <literal>()</literal>. Атрибуты принимают аргументы
либо в виде конкретных литеральных значений, либо константных выражений.
Аргументы разрешается записывать как позиционным, так и именованным синтаксисом.
</para>
<para>
Когда API-интерфейс модуля Reflection запрашивает экземпляр класса атрибута,
название атрибута трактуется как название запрашиваемого класса,
а аргументы атрибута передаются в конструктор этого класса.
Поэтому для каждого атрибута необходимо создавать класс.
</para>
<example>
<title>Синтаксис атрибутов</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>Чтение атрибутов через API-интерфейс модуля Reflection</title>
<para>
Для доступа к атрибутам классов, методов, функций, параметров, свойств
и констант класса в API-интерфейсе модуля Reflection предусмотрели метод
<function>getAttributes</function>. Метод возвращает массив
объектов <classname>ReflectionAttribute</classname>, каждый из которых
умеет возвращать название и аргументы атрибута, и создавать объект класса,
которым представили атрибут.
</para>
<para>
Разделение объекта, который представляет отражение атрибута, и объекта самого класса,
которым представили атрибут, повышает контроль над обработкой ошибок,
которые возникают, когда для атрибута не определили класс,
допустили опечатку или пропустили аргумент. Объект класса атрибута
создаётся и проверяется на корректность аргументов только после вызова
метода <function>ReflectionAttribute::newInstance</function>, не раньше.
</para>
<example>
<title>Пример чтения атрибутов через API-интерфейс модуля Reflection</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>
Вместо перебора каждого атрибута в объекте отражения
разрешается извлекать атрибуты только конкретного класса атрибутов.
Для этого в аргументе метода передают название конкретного класса атрибута.
</para>
<example>
<title>Пример чтения конкретных атрибутов через API-интерфейс модуля Reflection</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>Объявление классов атрибутов</title>
<para>
Хотя нет строго требования, лучше выполнять рекомендацию —
создавать отдельный класс для каждого атрибута. В самом простом случае
создают пустой класс, для которого объявляют атрибут <literal>#[Attribute]</literal>,
который доступен для импорта из глобального пространства имён через оператор use.
</para>
<example>
<title>Пример простого класса с атрибутом</title>
<programlisting role="php">
<![CDATA[
<?php
namespace Example;
use Attribute;
#[Attribute]
class MyAttribute {}
?>
]]>
</programlisting>
</example>
<para>
Объекты, для которых разрешат назначение атрибута, ограничивают
путём передачи битовой маски в первом аргументе объявления атрибута
<literal>#[Attribute]</literal>.
</para>
<example>
<title>Спецификация ограничения целей, доступных для присваивания атрибутов</title>
<programlisting role="php">
<![CDATA[
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class MyAttribute {}
?>
]]>
</programlisting>
<para>
Теперь назначение атрибута <classname>MyAttribute</classname> другому типу,
которой отличается от метода или функции, при вызове метода
<function>ReflectionAttribute::newInstance</function> выбросит исключение.
</para>
</example>
<para>Атрибутам доступны следующие цели:</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>
По умолчанию атрибут разрешается назначить классу, свойству или другому объекту рефлексии
только один раз. Назначить одинаковые атрибуты одному объекту рефлексии получится,
только если объявить атрибут <literal>#[Attribute]</literal>
с флагом <constant>Attribute::IS_REPEATABLE</constant> в битовой маске.
</para>
<example>
<title>
Пример с константой IS_REPEATABL, которая разрешит назначать атрибут многократно
</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
-->