mirror of
https://github.com/php/doc-ru.git
synced 2025-08-16 18:22:04 +00:00
391 lines
14 KiB
XML
391 lines
14 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">Reflection API</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>
|
||
Когда Reflection API запрашивает экземпляр класса атрибута, имя атрибута трактуется как имя запрашиваемого класса,
|
||
а аргументы атрибута передаются в конструктор этого класса.
|
||
Поэтому для каждого атрибута необходимо создавать класс.
|
||
</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>Чтение атрибутов с помощью Reflection API</title>
|
||
|
||
<para>
|
||
Для доступа к атрибутам классов, методов, функций, параметров, свойств и констант класса в Reflection API
|
||
существует метод <function>getAttributes</function>, который определен для каждого из перечисленных объектов рефлексии.
|
||
Этот метод возвращает массив объектов <classname>ReflectionAttribute</classname>, у каждого из которых
|
||
можно запросить имя и аргументы, а также создать объект класса, представляющего атрибут.
|
||
</para>
|
||
|
||
<para>
|
||
Отделение полученного через рефлексию представления атрибута от явного создания объекта даёт программисту более полный контроль
|
||
над обработкой ошибок, связанных с отсутствующими классами атрибутов, опечатками или отсутствующими аргументами.
|
||
Объект класса атрибута будет создан и проверен на корректность аргументов только после вызова
|
||
метода <function>ReflectionAttribute::newInstance</function>, не раньше.
|
||
</para>
|
||
|
||
<example>
|
||
<title>Чтение атрибутов средствами Reflection API</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>
|
||
Чтобы получить атрибуты только нужного класса,
|
||
вместо последовательного перебора всех атрибутов объекта рефлексии
|
||
в метод <function>getAttributes</function> передают в качестве аргумента имя искомого класса атрибута.
|
||
</para>
|
||
|
||
<example>
|
||
<title>Чтение конкретных атрибутов средствами Reflection API</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_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
|
||
-->
|