mirror of
https://github.com/php/doc-ru.git
synced 2026-03-23 23:32:16 +01:00
382 lines
14 KiB
XML
382 lines
14 KiB
XML
<?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
|
||
-->
|