Атрибути · PHP Manual
Атрибути (attributes) PHP дають структуровані, машинозчитувані метадані для класів, методів, функцій, параметрів, властивостей і констант. Їх можна читати під час виконання через Reflection API, що дозволяє змінювати поведінку динамічно, не правлячи код. Атрибути - декларативний спосіб додати до коду метадані.
Атрибути розділяють реалізацію можливості та її використання. Інтерфейси задають структуру, вимагаючи наявності методів; атрибути додають метадані до різних елементів мови, зокрема до методів, функцій, властивостей і констант. На відміну від інтерфейсів, які змушують реалізувати методи, атрибути анотують код, не змінюючи його структури.
Атрибути можуть доповнити або замінити необовʼязкові методи інтерфейсу: замість вимушеної структури вони дають метадані. Візьмімо інтерфейс ActionHandler, що представляє певну операцію в застосунку. Частина реалізацій потребує кроку підготовки, частина ні. Замість того щоб усі класи, які реалізують ActionHandler, оголошували метод setUp(), вимоги до підготовки можна позначити атрибутом. Так гнучкості більше, до того ж один атрибут за потреби застосовується кілька разів.
Приклад: реалізація необовʼязкових методів інтерфейсу за допомогою атрибутів
<?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);
Синтаксис атрибутів
Синтаксис атрибута складається з кількох частин. Оголошення починається з #[ і закінчується ]. Усередині можна перелічити один або кілька атрибутів через кому. Імʼя атрибута буває некваліфікованим, кваліфікованим або повністю кваліфікованим - так, як описано в розділі Основи використання просторів імен. Аргументи атрибута необовʼязкові й беруться в дужки (). Аргументами можуть бути лише літеральні значення або константні вирази. Підтримується синтаксис як позиційних, так і іменованих аргументів.
Імʼя атрибута та його аргументи розвʼязуються в клас, і аргументи передаються в конструктор цього класу, коли екземпляр атрибута запитують через Reflection API. Тому для кожного атрибута рекомендується створювати окремий клас.
Приклад: синтаксис атрибутів
<?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
{
}
Читання атрибутів через Reflection API
Щоб дістати атрибути класів, методів, функцій, параметрів, властивостей і констант класу, використовуйте метод getAttributes() з Reflection API. Він повертає масив екземплярів ReflectionAttribute. У цих екземплярів можна запитати імʼя атрибута та аргументи, а також створити за ними екземпляр самого атрибута.
Відокремлення рефлексійного представлення атрибута від його справжнього екземпляра дає більше контролю над обробкою помилок: відсутній клас атрибута, аргументи з неправильними типами, пропущені значення. Обʼєкти класу атрибута створюються лише після виклику ReflectionAttribute::newInstance(), тож аргументи перевіряються саме в цей момент.
Приклад: читання атрибутів через Reflection API
<?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)
}
Замість перебору всіх атрибутів на екземплярі рефлексії можна отримати лише атрибути конкретного класу, передавши імʼя цього класу аргументом.
Приклад: читання конкретних атрибутів через Reflection API
<?php
#[Attribute]
class MyAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
#[MyAttribute(value: 1234)]
class Thing
{
}
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));
Оголошення класів атрибутів
Для кожного атрибута рекомендується мати окремий клас. У найпростішому випадку достатньо порожнього класу з оголошенням #[Attribute]. Сам атрибут імпортується з глобального простору імен інструкцією use.
Приклад: простий клас атрибута
<?php
namespace Example;
use Attribute;
#[Attribute]
class MyAttribute
{
}
Щоб обмежити типи оголошень, до яких можна застосувати атрибут, передайте бітову маску першим аргументом оголошення #[Attribute].
Приклад: обмеження місць використання атрибута через target
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class MyAttribute
{
}
Оголошення MyAttribute на іншому типі тепер призведе до винятку під час виклику ReflectionAttribute::newInstance().
Доступні такі цілі:
Attribute::TARGET_CLASSAttribute::TARGET_FUNCTIONAttribute::TARGET_METHODAttribute::TARGET_PROPERTYAttribute::TARGET_CLASS_CONSTANTAttribute::TARGET_PARAMETERAttribute::TARGET_ALL
Типово атрибут можна використати лише один раз на одне оголошення. Щоб дозволити повторюване використання, додайте в бітову маску оголошення #[Attribute] прапорець Attribute::IS_REPEATABLE.
Приклад: IS_REPEATABLE дозволяє атрибут на оголошенні кілька разів
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class MyAttribute
{
}
Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.