mirror of
https://github.com/php/doc-ru.git
synced 2025-08-16 18:22:04 +00:00
408 lines
15 KiB
XML
408 lines
15 KiB
XML
<?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
|
||
-->
|