Руководство по PHP атрибутам

Источник: «A Guide to PHP Attributes»
Узнайте, что такое PHP атрибуты и как их использовать. Также рассмотрим, как создавать собственные PHP атрибуты.

Введение

При создании веб-приложений на PHP могут возникнуть ситуации, когда потребуется добавить метаданные для аннотирования кода, которые можно будет прочитать в других частях приложения. Например, если вы когда-либо использовали PHPUnit для написания тестов PHP-кода, то, скорее всего, использовали аннотацию @test, чтобы отметить метод как тест в DocBlock.

Традиционно подобные аннотации добавлялись в код с помощью DocBlocks. Однако начиная с версии PHP 8.0, можно использовать атрибуты для более структурированного аннотирования кода.

В этой статье рассмотрим, что такое атрибуты, каково их назначение и как их использовать. Также рассмотрим, как создавать собственные атрибуты и как тестировать код, использующий атрибуты.

К концу данной статьи вы должны почувствовать себя достаточно уверенно, чтобы начать использовать атрибуты в собственных PHP-приложениях.

Что такое атрибуты

Атрибуты были добавлены в PHP в версии PHP 8.0 (выпущена в ноябре 2020 года). Они позволяют добавлять в код машиночитаемые, структурированные метаданные. Их можно применять к классам, методам классов, функциям, анонимным функциям, свойствам и константам. Они имеют синтаксис #[AttributeNameHere], где AttributeNameHere — имя атрибута. Например: #[Test], #[TestWith('data')], #[Override] и так далее.

Чтобы понять, что это такое и какую проблему они решают, полезно сравнить их с "тегами" в DocBlocks. Поэтому для начала давайте рассмотрим пример теста PHPUnit, в котором используется DocBlock:

/**
* @test
* @dataProvider permissionsProvider
*/

public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

public static function permissionsProvider(): array
{
return [
['superadmin'],
['admin'],
['user'],
// и тому подобное...
];
}

В приведённом выше коде мы создали два метода:

Применяя тег @test в DocBlock к тестовому методу, PHPUnit распознает этот метод как тест, который может быть запущен. Аналогично, применяя тег @dataProvider в DocBlock к тестовому методу, PHPUnit будет использовать метод permissionsProvider в качестве источника данных для тестового метода.

Поскольку метод источника данных permissionsProvider возвращает массив с тремя элементами, это означает, что тест будет запущен три раза (по одному разу для каждого из разрешений).

Хотя теги в DocBlocks могут быть использованы для аннотирования кода, они ограничены в том смысле, что представляют собой обычный текст. Как следствие, в них может отсутствовать структура, и их легко перепутать. Например, давайте намеренно добавим опечатку в тег @test в DocBlock:

/**
* @tests
* @dataProvider permissionsProvider
*/

public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

В приведённом выше примере кода мы переименовали @test в @tests. Если бы заглянули в тестовый класс, содержащий множество тестов, то, скорее всего, не заметили бы этой опечатки. Если попытаемся запустить этот тест, он не будет запущен, потому что PHPUnit не распознает его как тест.

Именно здесь могут пригодиться атрибуты. Продолжая пример с PHPUnit, давайте обновим метод тестирования, чтобы использовать атрибуты, а не DocBlock. Начиная с PHPUnit 10, можно использовать атрибут #[\PHPUnit\Framework\Attributes\Test], чтобы отметить метод как тестовый. Также можно использовать атрибут #[\PHPUnit\Framework\Attributes\DataProvider()], чтобы указать метод в качестве источника данных для теста. Обновлённый код будет выглядеть следующим образом:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;

#[Test]
#[DataProvider('permissionsProvider')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

Как видно из приведённого выше примера кода, к методу тестирования были добавлены два атрибута:

Возможно, вы также заметили, что эти атрибуты имеют пространство имён и импортируются так же, как и классы. Это происходит потому, что атрибуты на самом деле являются классами; обсудим это более подробно позже.

Если атрибут #[Test] будет переименован в #[Tests], это будет распознано как ошибка в IDE и выделено (обычно красным подчёркиванием), чтобы было легче её обнаружить и исправить. Это происходит потому, что PHP попытается найти атрибут Tests в текущем пространстве имён. Например, если представить, что тест находится в пространстве имён Tests\Feature\Authorization, PHP попытается разрешить атрибут Tests\Feature\Authorization\Tests. Однако в данном случае будем считать, что атрибут #[Tests] не существует. Это похоже на то, что происходит, если попытаться сослаться в коде на несуществующий класс. Однако если попытка использовать несуществующий класс приведёт к тому, что во время выполнения будет выброшено исключение, то попытка использовать несуществующий атрибут — нет. Таким образом, тест всё равно не будет выполнен (аналогично DocBlocks), но IDE сможет выделить проблему, чтобы было проще её решить.

Важно помнить, что если вы явно не проверяете атрибуты в коде во время выполнения, PHP не будет выбрасывать исключение, если атрибут не существует.

Ещё одна полезная особенность атрибутов заключается в том, что они могут принимать аргументы (как показано в примере выше с атрибутом #[DataProvider]). Благодаря структурированному характеру атрибутов IDE, как правило, может показать предложения по автозаполнению аргументов, которые принимает атрибут. Это может быть удобным, если вы не уверены, какие аргументы принимает атрибут.

Как использовать атрибуты

Теперь, когда получено краткое представление, что такое атрибуты, давайте посмотрим, как можно использовать их в своём коде.

Применение атрибутов к коду

Как было сказано выше, атрибуты могут применяться к классам, методам классов, функциям, анонимным функциям, свойствам и константам. Давайте посмотрим, как это может выглядеть на примере атрибута #[MyAwesomeAttribute].

Атрибут можно применить к классу, добавив его непосредственно над объявлением класса:

#[MyAwesomeAttribute]
class MyClass
{
// Код класса...
}

Атрибут можно применить к методу класса, добавив его непосредственно над объявлением метода:

class MyClass
{
#[MyAwesomeAttribute]
public function myMethod()
{
// Код метода...
}
}

Атрибут можно применить к функции, добавив его непосредственно над объявлением функции:

#[MyAwesomeAttribute]
function myFunction()
{
// Код функции...
}

Атрибут можно применить к анонимной функции, добавив его непосредственно перед объявлением анонимной функции:

$myFunction = #[MyAwesomeAttribute] function () {
// Код анонимной функции...
};

Атрибут можно применить к свойству, добавив его непосредственно над объявлением свойства:

class MyClass
{
#[MyAwesomeAttribute]
public string $myProperty;
}

Атрибут можно применить к константе, добавив его непосредственно над объявлением константы:

class MyClass
{
#[MyAwesomeAttribute]
public const MY_CONSTANT = 'my-constant';
}

Атрибут также можно применить к аргументам функции. Например, начиная с PHP 8.2, можно использовать атрибут PHP #[\SensitiveParameter], чтобы значение, переданное в аргумент функции, не было доступно или записано в стектрейс. Это может быть важно, если у есть метод, принимающий поле, например, пароль, и вы не хотите, чтобы его значение записывалось в стектрейс. Атрибут можно применить к аргументу функции следующим образом:

function authenticate(
string $username,
#[\SensitiveParameter] string $password,
) {
// Код функции...
}

Передача параметров атрибутам

Как было показано ранее, в атрибуты можно передавать параметры, чтобы они могли быть прочитаны кодом, использующим их. Продолжая предыдущий пример с PHPUnit, можно увидеть, что в атрибут #[DataProvider] можно передать имя метода источника данных:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;

#[Test]
#[DataProvider('permissionsProvider')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

Как и в случае с функциями в PHP, атрибуты поддерживают использование именованных аргументов. Можно переписать приведённый выше пример кода, чтобы использовать именованные аргументы следующим образом:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;

#[Test]
#[DataProvider(method: 'permissionsProvider')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

Как видно из примера выше, используется именованный аргумент method:. В результате это означает, что можно улучшить читабельность кода, если неясно, для чего нужен аргумент.

Также атрибуту можно передать несколько аргументов, если он их ожидает. Например, можно удалить атрибут #[DataProvider] из теста и вместо него жёстко закодировать значения с помощью атрибута #[TestWith]:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

#[Test]
#[TestWith('superadmin', 'admin', 'user')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): void {
// Здесь выполняется тестирование...
}

Одновременное использование нескольких атрибутов

Если используется несколько атрибутов для одной цели (например, класс, метод, функция и т. д.), можно либо определить их все в одном блоке #[...], либо использовать отдельный блок для каждого атрибута.

Для применения атрибутов в отдельных блоках #[...] можно поступить следующим образом:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

#[Test]
#[TestWith('superadmin', 'admin', 'user')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
):
{
// Здесь выполняется тестирование...
}

Для применения атрибутов в одном и том же блоке #[...] можно поступить следующим образом:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

#[
Test,
TestWith('superadmin', 'admin', 'user')
]

public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
):
{
// Здесь выполняется тестирование...
}

Как создавать собственные атрибуты

Теперь, когда мы узнали о том, как применяются атрибуты, давайте разберёмся, как можно создавать собственные атрибуты, а затем читать их в коде.

Будем использовать атрибуты для добавления удобных для человека меток к перечислениям. Представим, что эти метки будут использоваться при отображении перечисления пользователю (например, в выпадающем меню формы).

Допустим, мы создаём платформу для ведения блогов, и у нас есть перечисление PostStatus, представляющее статус записи в блоге. Может возникнуть ситуация, когда необходимо отобразить пользователю значение перечисления PostStatus записи в блоге. Давайте посмотрим на перечисление:

// app/Enums/PostStatus.php

namespace App\Enums;

enum PostStatus: string
{
case Draft = 'draft';

case Published = 'published';

case InReview = 'in-review';

case Scheduled = 'scheduled';
}

Как видно из примера кода выше, мы создали перечисление PostStatus, которое содержит четыре значения. В настоящее время значения являются строковыми, то есть мы можем сделать что-то вроде $postStatus = PostStatus::InReview->value, чтобы получить строковое значение (in-review) для значения InReview. Но мы можем захотеть получить больше контроля над возвращаемым строковым значением. Например, возможно, мы захотим возвращать более удобную для человека метку, такую как In Review вместо in-review.

Для этого создадим новый атрибут Friendly, который можно будет применять к элементам перечисления. Этот атрибут будет принимать строковый аргумент, используемый в качестве удобной для восприятия метки.

Сначала необходимо создать атрибут. В PHP атрибуты — это просто классы, аннотированные атрибутом #[\Attribute]. Поэтому создадим новый атрибут Friendly и поместим его в пространство имён App\Attributes:

// app/Attributes/Friendly.php

namespace App\Attributes;

use Attribute;

#[Attribute]
final readonly class Friendly
{
public function __construct(
public string $friendly,
) {
//
}
}

Как видно выше, мы только что создали новый класс Friendly с конструктором, принимающим единственный строковый аргумент. Этот аргумент будет использоваться в качестве удобной для восприятия человеком метки для значения перечисления.

Затем можно применить атрибут Friendly к перечислению следующим образом:

// app/Enums/PostStatus.php

namespace App\Enums;

use App\Attributes\Friendly;

enum PostStatus: string
{
#[Friendly('Draft')]
case Draft = 'draft';

#[Friendly('Published')]
case Published = 'published';

#[Friendly('In Review')]
case InReview = 'in-review';

#[Friendly('Scheduled to Publish')]
case Scheduled = 'scheduled';
}

В приведённом выше примере кода мы присвоили атрибут Friendly каждому значению перечисления. Например, дружественная метка для случая PostStatus::ScheduledScheduled to Publish.

Теперь, когда атрибуты назначены, необходимо иметь возможность читать их в коде. Создадим новый метод friendly в перечислении PostStatus, позволяющий использовать код типа PostStatus::InReview->friendly() для получения дружелюбной метки.

Давайте посмотрим на готовое перечисление с таким кодом, а затем обсудим, что было сделано:

// app/Enums/PostStatus.php

namespace App\Enums;

use App\Attributes\Friendly;
use ReflectionClassConstant;

enum PostStatus: string
{
#[Friendly('Draft')]
case Draft = 'draft';

#[Friendly('Published')]
case Published = 'published';

#[Friendly('In Review')]
case InReview = 'in-review';

#[Friendly('Scheduled to Publish')]
case Scheduled = 'scheduled';

public function friendly(): string
{
// Создание экземпляра ReflectionClassConstant для значения
// перечисления и попытка чтения атрибута Friendly.
$attributes = (new ReflectionClassConstant(
class: self::class,
constant: $this->name,
))->getAttributes(
name: Friendly::class,
);

// Если атрибут Friendly не найден для значения перечисления,
// выбрасывается исключение.
if ($attributes === []) {
throw new \RuntimeException(
message: 'No friendly attribute found for ' . $this->name,
);
}

// Создание нового экземпляра атрибута Friendly и
// возвращение значения friendly.
return $attributes[0]->newInstance()->friendly;
}
}

В приведённом выше примере кода мы добавили новый метод friendly, возвращающий строку. Начинаем с создания нового экземпляра ReflectionClassConstant, позволяющего проверить конкретный элемент перечисления, для которого вызывается этот метод. Затем используем метод getAttributes, чтобы попытаться прочитать атрибут Friendly. Стоит отметить, что метод getAttributes возвращает массив экземпляров ReflectionAttribute. Если атрибут не найден (например, потому что разработчик не добавил атрибут в регистр перечисления), то будет возвращён пустой массив.

Затем проверяем, был ли найден атрибут, проверяя, пуст ли массив $attributes. Если массив пуст, выбрасываем исключение \RuntimeException. Таким образом, убеждаемся, что атрибут Friendly должен быть добавлен в перечисление. Однако в зависимости от вашего случая использования, возможно, потребуется вернуть значение по умолчанию. Например, можно применить некоторое форматирование к значению элемента перечисления и вернуть его.

Если найден атрибут Friendly, то $attributes[0] будет экземпляром класса ReflectionAttribute. Затем можно использовать метод newInstance, чтобы создать новый экземпляр атрибута Friendly и вернуть его свойство friendly.

Это позволяет писать такой код, как:

echo PostStatus::InReview->friendly(); // In Review
echo PostStatus::Scheduled->friendly(); // Scheduled to Publish

Поскольку добавление "дружественной" метки к перечислению может быть распространённым случаем использования, можно извлечь это в трейт, который будет использоваться в других классах перечислений. Например, можно создать трейт App\Traits\HasFriendlyEnumLabels, который затем будет использоваться в классах перечислений:

//app/Traits/HasFriendlyEnumLabels.php

namespace App\Traits;

use App\Attributes\Friendly;
use ReflectionClassConstant;

trait HasFriendlyEnumLabels
{
public function friendly(): string
{
// Создание экземпляра ReflectionClassConstant для значения
// перечисления и попытка чтения атрибута Friendly.
$attributes = (new ReflectionClassConstant(
class: self::class,
constant: $this->name,
))->getAttributes(
name: Friendly::class,
);

// Если атрибут Friendly не найден для значения перечисления,
// выбрасывается исключение.
if ($attributes === []) {
throw new \RuntimeException(
message: 'No friendly attribute found for ' . $this->name,
);
}

// Создание нового экземпляра атрибута Friendly и
// возвращение значения friendly.
return $attributes[0]->newInstance()->friendly;
}
}

Его можно использовать в перечислении PostStatus следующим образом:

// app/Enums/PostStatus.php

namespace App\Enums;

use App\Attributes\Friendly;
use App\Traits\HasFriendlyEnumLabels;

enum PostStatus: string
{
use HasFriendlyEnumLabels;

#[Friendly('Draft')]
case Draft = 'draft';

#[Friendly('Published')]
case Published = 'published';

#[Friendly('In Review')]
case InReview = 'in-review';

#[Friendly('Scheduled to Publish')]
case Scheduled = 'scheduled';
}

Как видно из примера кода, благодаря этому перечисление выглядит намного чище и легче читается.

Объявление цели атрибута

Как уже говорилось, атрибуты можно применять ко многим частям кода, таким как классы, методы классов, свойства и т. д. Однако бывают случаи, когда необходимо, чтобы атрибут использовался только в определённом контексте. Например, мы ожидаем, что атрибут Friendly будет использоваться только со значениями перечисления. Мы не ожидаем, что он будет использоваться, например, в классе или методе класса.

PHP позволяет опционально указывать цель для атрибута при его объявлении. Давайте обновим атрибут Friendly, а затем углубимся в эту тему. Поскольку мы хотим, чтобы атрибут Friendly использовался только в перечислениях, можно обновить его, указав следующее:

// app/Attributes/Friendly.php

namespace App\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
final readonly class Friendly
{
public function __construct(
public string $friendly,
) {
//
}
}

В классе атрибутов Friendly видно, что в объявлении #[Attribute] передаётся параметр, указывающий, что он должен использоваться только для констант класса (что позволит использовать его в перечислениях).

Можно указать следующие цели для атрибута:

Хотя указание цели необязательно, я предпочитаю указывать её каждый раз, при создании атрибута. Потому что это позволяет выразиться яснее и облегчает другим разработчикам понимание того, как этот атрибут должен использоваться. Это просто личное предпочтение.

Важно помнить, что если применить атрибут к неправильному типу цели, PHP, как правило, не будет выбрасывать исключение во время выполнения, если только вы не взаимодействуете с ним. Предположим, мы установили для атрибута Friendly значение Attribute::TARGET_CLASS (намереваясь, чтобы он применялся только к классам). Этот код всё равно будет работать, потому что мы никак не взаимодействуем с атрибутом:

echo PostStatus::InReview->value; // in-review

Однако выполнение этого кода приведёт к выбрасыванию Error:

echo PostStatus::InReview->friendly();

Сообщение об ошибке будет выглядеть так:

Attribute "App\Attributes\Friendly" cannot target class constant (allowed targets: class)

Объявление повторяемых атрибутов

Бывают случаи, когда нужно разрешить применять атрибут несколько раз к одной и той же цели. Например, возьмём атрибут #[TestWith] от PHPUnit. Его можно применить один раз и передать ему несколько аргументов или применить несколько раз с одним аргументом. Например, можно сделать так:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

#[Test]
#[TestWith('superadmin', 'admin', 'user')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): {
// Здесь выполняется тестирование...
}

Или можно сделать так:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

#[Test]
#[TestWith('superadmin')]
#[TestWith('admin')]
#[TestWith('user')]
public function access_is_denied_if_the_user_does_not_have_permission(
string $permission
): {
// Здесь выполняется тестирование...
}

Оба этих примера кода приведут к тому, что тест будет запущен три раза (по одному разу для каждого из переданных разрешений).

Чтобы указать, что определённый атрибут может быть применён несколько раз к одной и той же цели, используется декларация #[Attribute(Attribute::IS_REPEATABLE)]. Например, декларация для атрибута #[TestWith] в PHPUnit выглядит следующим образом:

namespace PHPUnit\Framework\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class TestWith
{
// ...
}

Как видно из примера кода, атрибут #[TestWith] предназначен для применения к методам класса и может быть применён несколько раз к одной и той же цели (в данном случае к методу класса). Это определяется битовой маской Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE, передаваемой атрибуту #[Attribute].

Если флаг Attribute::IS_REPEATABLE не используется и атрибут повторяется на одной и той же цели, IDE обычно выделяет его как ошибку.

Тестирование кода, использующего атрибуты

Как и любой другой элемент PHP приложения, написание тестов для кода, взаимодействующего с атрибутами, очень важно. Тесты помогут удостовериться в том, что атрибуты применены правильно и что они также правильно считываются.

Продолжая рассматривать пример с атрибутом Friendly, давайте представим, что необходимо написать несколько тестов для перечисления PostStatus и трейта HasFriendlyEnumLabels.

Начнём с написания теста для трейта HasFriendlyEnumLabels. Необходимо проверить два разных сценария:

Тестовый класс может выглядеть так:

namespace Tests\Feature\Traits\HasFriendlyEnumLabels;

use App\Attributes\Friendly;
use App\Traits\HasFriendlyEnumLabels;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

final class FriendlyTest extends TestCase
{
#[Test]
public function friendly_label_can_be_returned(): void
{
$this->assertSame(
'Has Friendly Value',
MyTestEnum::HasFriendlyValue->friendly(),
);
}

#[Test]
public function error_is_thrown_if_the_friendly_attribute_is_not_applied(): void
{
$this->expectException(\RuntimeException::class);

$this->expectExceptionMessage(
'No friendly attribute found for HasNoFriendlyValue',
);

MyTestEnum::HasNoFriendlyValue->friendly();
}
}

enum MyTestEnum: string
{
use HasFriendlyEnumLabels;

#[Friendly('Has Friendly Value')]
case HasFriendlyValue = 'has-friendly-value';

case HasNoFriendlyValue = 'has-no-friendly-value';
}

Как видно из приведённого выше примера кода, мы написали два теста (по одному для проверки каждого из сценариев, указанных выше). Это означает, что можно быть уверенным в том, что трейт HasFriendlyEnumLabels работает так, как ожидалось.

Вы, наверное, заметили, что для проверки этого трейта было создано новое перечисление MyTestEnum и помещено в самый низ PHP файла после класса теста. Это связано с тем, что необходимо специально создать новое перечисление, которое будет содержать элемент, к которому не будет применён атрибут Friendly. Это позволит протестировать, что исключение будет выброшено, если атрибут не будет применён. Хотя, как правило, я не рекомендую добавлять несколько классов в один файл, в данном случае это вполне допустимо, поскольку он используется только в целях тестирования. Однако если вас не устраивает такой подход, возможно, стоит подумать о создании отдельного файла для тестового перечисления.

Также можно написать несколько тестов для перечисления PostStatus, чтобы убедиться, что ко всем элементам применён атрибут Friendly. Давайте посмотрим на тестовый класс, а затем обсудим, что в нём делается:

namespace Tests\Feature\Enums\PostStatus;

use App\Enums\PostStatus;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

final class FriendlyTest extends TestCase
{
#[Test]
#[DataProvider('casesProvider')]
public function each_case_has_a_friendly_label(
PostStatus $postStatus
): void {
$this->assertNotNull($postStatus->friendly();
}

public static function casesProvider(): array
{
return array_map(
callback: static fn (PostStatus $case): array => [$case],
array: PostStatus::cases(),
);
}
}

В этом тесте используется атрибут PHPUnit #[DataProvider] для указания источника данных для метода тестирования. В источнике данных (метод casesProvider) создаётся массив массивов, содержащих по одному элементу (enum case), чтобы PHPUnit мог передавать эти элементы в сам метод тестирования. Возвращаемый массив будет выглядеть так:

[
[PostStatus::Draft],
[PostStatus::Published],
[PostStatus::InReview],
[PostStatus::Scheduled],
]

Это означает, что метод тестирования будет запущен четыре раза (по одному разу для каждого значения перечисления). Затем вызываем метод friendly для каждого элемента перечисления и просто утверждаем, что результат не null. Хотя этот тест не проверяет явно возвращаемые строки, он очень важен для обеспечения уверенности в том, что атрибут Friendly был применён к каждому из элементов перечисления. Это означает, что если в будущем добавить новый элемент перечисления и забыть добавить к нему атрибут Friendly, этот тест не сработает, и можно будет исправить ошибку до того, как код будет развернут в продакшн.

В зависимости от того, как используются атрибуты в коде, может потребоваться добавить более явные тесты. Однако для данной конкретной ситуации я считаю, что эти тесты обеспечивают достаточно высокий уровень уверенности в том, что код работает так, как ожидалось.

Заключение

В этой статье мы рассмотрели, что такое атрибуты, каково их назначение в коде и как их использовать. Кроме того, выяснили, как создавать собственные атрибуты и как тестировать код, использующий атрибуты.

Теперь вы должны чувствовать себя достаточно уверенно, чтобы начать использовать атрибуты в своих PHP приложениях.

Дополнительные материалы

Предыдущая Статья

Введение в Alpine.js

Следующая Статья

Маска изображения довольно удобна