1
0
mirror of https://github.com/php/doc-ru.git synced 2026-03-23 23:32:16 +01:00
Files
archived-doc-ru/language/attributes.xml

382 lines
14 KiB
XML
Raw Permalink 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: 0f14761ba340c6e49797706ac3f0cf1147d97253 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>
PHP-атрибуты — структурированные машиночитаемые метаданные для классов, методов,
функций, параметров, свойств и констант. Атрибуты инспектируют при выполнении кода
<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("Файл не существует");
}
}
#[SetUp]
public function targetDirectoryExists()
{
if (!file_exists($this->targetDirectory)) {
mkdir($this->targetDirectory);
} elseif (!is_dir($this->targetDirectory)) {
throw new RuntimeException("Целевой путь $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 = $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>
Отделение отражённого представления атрибута от экземпляра самого́ атрибутного класса
повышает контроль над обработкой ошибок, которые возникают, когда для атрибута не определили класс,
допустили опечатку в названии аргумента или пропустили значение аргумента. PHP создаёт объекты атрибутных классов и проверяет
корректность аргументов только после вызова метода <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>
Вызов метода <function>ReflectionAttribute::newInstance</function> теперь выбросит исключение
при объявлении атрибута <classname>MyAttribute</classname> для другого типа,
кроме метода или функции.
</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_REPEATABLE, которая разрешит назначать атрибут многократно
</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
-->