PHP 8.0: Атрибути (3/4)
Прямо зараз випускається версія PHP 8.0. У ній багато нового, як у жодній іншій версії. Їх представлення заслуговує на чотири окремі статті. У третій частині ми розглянемо атрибути.
Атрибути надають абсолютно новий спосіб написання структурованих метаданих для класів і всіх його членів, а також функцій, закриття і їх параметрів. Досі для цього використовувалися коментарі PhpDoc, але їх синтаксис завжди був настільки вільним і непослідовним, що їх неможливо було запустити в машинну обробку. Тому на зміну їм приходять атрибути з визначеним синтаксисом і підтримкою в класах рефлексії.
Завдяки цьому бібліотеки, які раніше
отримували метадані шляхом розбору
коментарів phpDoc, зможуть замінити їх на
атрибути. Одним з прикладів є Nette, де в
останніх версіях Application та DI вже можна
використовувати атрибути Persistent
,
CrossOrigin
та Inject
замість
анотацій @persistent
, @crossOrigin
та
@inject
.
Код з використанням анотацій:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Те саме з атрибутами:
use Nette\Application\Attributes\CrossOrigin;
use Nette\Application\Attributes\Persistent;
use Nette\DI\Attributes\Inject;
#[Persistent('comp1', 'comp2')]
class SomePresenter
{
#[Persistent]
public int $page = 1;
#[Inject]
public Facade $facade;
#[CrossOrigin]
public function handleSomething()
{
}
}
PHP оцінює імена атрибутів так само, як якщо
б вони були класами, в контексті просторів
імен і речень use
. Тож їх навіть можна
було б написати, наприклад, так:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Клас, що представляє атрибут, може існувати, а може і не існувати. Але краще, якщо він існує, тому що тоді редактор може підказати його під час написання, статичний аналізатор розпізнає помилки тощо.
Синтаксис
Дуже зручно, що PHP до версії 8 сприймає атрибути лише як коментарі, тому їх можна використовувати у коді, який мав би працювати у старіших версіях.
Синтаксис окремого атрибута виглядає як
створення екземпляра об'єкта, якщо опустити
оператор new
. Отже, ім'я класу, за яким
слідують необов'язкові аргументи в круглих
дужках:
#[Column('string', 32, true, false)]#
protected $username;
І ось тут можна застосувати нову гарячу функцію PHP 8.0 – іменовані аргументи:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Кожен елемент може мати декілька атрибутів, які можна писати окремо або через кому:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Наступний атрибут застосовується до всіх трьох властивостей:
#[Common]
private $a, $b, $c;
Прості вирази та константи, які можна обчислити під час компіляції і які використовуються як значення за замовчуванням для властивостей, можна використовувати як аргументи в атрибутах:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
На жаль, значенням аргументу не може бути інший атрибут, тобто атрибути не можуть бути вкладеними. Наприклад, не існує прямого способу перетворення наступної анотації, що використовується у Доктрині, на атрибути:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Також не існує еквівалента атрибуту для файлу phpDoc, тобто коментаря, розташованого на початку файлу, який використовується, наприклад, Nette Tester.
Відображення атрибутів
За допомогою рефлексії можна визначити,
які атрибути мають окремі елементи. У
класах з рефлексією з'явився новий метод
getAttributes()
, який повертає масив
об'єктів ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // full attribute name, e.g. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // returns an instance: new MyAttributes\Example('Hello', 123)
}
Атрибути, що повертаються, можна
відфільтрувати за допомогою параметра,
наприклад, $reflection->getAttributes(Example::class)
повертає тільки атрибути Example
.
Класи атрибутів
Атрибутивний клас MyAttributes\Example
може
не існувати. Тільки виклик методу
newInstance()
вимагає його існування,
оскільки він його інстанціює. Отже, давайте
напишемо його. Це буде абсолютно звичайний
клас, тільки ми повинні надати атрибут
Attribute
(тобто з глобального
системного простору імен):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Можна обмежити, для яких елементів мови буде дозволено використовувати цей атрибут. Наприклад, лише для класів та властивостей:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Доступні прапори TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
та за замовчуванням
TARGET_ALL
.
Але будьте обережні, перевірка
правильності чи неправильності
використання відбувається, на диво, лише
при виклику методу newInstance()
. Сам
компілятор цієї перевірки не виконує.
Майбутнє з атрибутами
Завдяки атрибутам і новим
типам, коментарі до документації PHP
вперше в своїй історії стануть дійсно
тільки документальними коментарями. PhpStorm
вже поставляється з кастомними
атрибутами, які можуть замінити,
наприклад, анотацію @deprecated
. І можна
припустити, що колись цей атрибут буде в PHP
за замовчуванням. Аналогічно будуть
замінені й інші анотації, такі як
@throws
тощо.
Хоча Nette використовує анотації з найпершої версії для позначення постійних параметрів і компонентів, вони не використовувалися більш масово, оскільки не були конструкцією рідної мови, тому редактори не пропонували їх, і було легко помилитися. Хоча це вже вирішується за допомогою плагінів для редакторів, справді рідний спосіб, який приносять атрибути, відкриває абсолютно нові можливості.
До речі, атрибути отримали виняток у
стандарті кодування Nette, який вимагає, щоб
ім'я класу, окрім специфіки (наприклад,
Product
, InvalidValue
), містило ще й
узагальнення (наприклад, ProductPresenter
,
InvalidValueException
). Інакше при
використанні в коді буде незрозуміло, що
саме представляє клас. Для атрибутів це не
бажано, тому клас називається Inject
замість InjectAttribute
.
*В останній частині ми розглянемо, які нові функції і класи з'явилися в PHP і познайомимося з компілятором Just in Time.
Щоб залишити коментар, будь ласка, увійдіть до системи