Расширение перечислений PHP 8.1 с помощью атрибутов
С релизом PHP 8.1 язык получил встроенную поддержку перечислений. Перечисления (Enums
) — это типобезопасный, удобочитаемый и эффективный способ инкапсулирования небольшого набора возможных значений, которые может принимать поле в вашей модели данных. Использование классов вместо перечислений базы данных обеспечивает большую гибкость, если вам нужно будет расширить список в будущем.
Например, у вас есть модель данных пользователя, и у этого пользователя может быть определённый список ролей, из которых вы можете выбирать.
namespace App\Enums;
enum UserRole: string
{
case Admin = 'admin';
case TeamAdmin = 'team_admin';
case Support = 'support';
case Basic = 'basic';
}
В вашей модели данных Laravel также поддерживает перечисления, приводя их к значению, если вы определяете его в массиве приведений.
/**
* Атрибуты, которые должны быть приведены.
*
* @var array<string,string|class-string>
*/
protected $casts = [
'role' => UserRole::class,
];
Добавление приведения перечисления гарантирует, что будет выдано исключение, если мы попытаемся сохранить роль в нашей пользовательской модели, не определённой в перечислении.
Очень практичное использование перечислений заключается в создании значений для раскрывающегося списка в HTML.
<select name=”roles”>
@foreach(UserRole::cases() as $role)
<option value="{{ $role->value }}">{{ $role->name }}</option>
@endforeach
</select>
На первый взгляд, в приведённом выше примере нет ничего плохого, пока вы не посмотрите на видимое имя каждой опции в раскрывающемся списке. Admin
и TeamAdmin
— отличные имена переменных, но Administrator
и Team Administrator
лучше выглядят в пользовательском интерфейсе, чтобы было совершенно ясно, какая роль принадлежит человеку, управляющему ролями пользователей.
Хотя перечисления отлично подходят для простых пар имя/значение, в подобных случаях, когда вам нужно будет добавить третье свойство, придётся проявить творческие способности.
Введите атрибуты PHP. Заимствованный из концепции аннотаций в других языках, это способ связать метаданные со свойствами, методами и классами, что звучит именно так, как нам нужно.
Во-первых, нам нужно создать атрибут Description
.
namespace App\Enums\Attributes;
use Attribute;
#[Attribute]
class Description
{
public function __construct(
public string $description,
) {
}
}
Теперь у нас есть атрибут Description, который мы можем использовать в нашем перечислении для определения удобных для пользователя, нужных нам имён ролей.
namespace App\Enums;
enum UserRole: string
{
#[Description('Administrator')]
case Admin = 'admin';
#[Description('Team Administrator')]
case TeamAdmin = 'team_admin';
case Support = 'support';
case basic = 'basic';
}
Теперь нам нужно получить эти атрибуты, что можно сделать только с помощью отражения. Поскольку мы можем захотеть повторно использовать этот атрибут в других перечислениях, можно создать трейт для упрощения задачи.
namespace App\Enums\Concerns;
use Illuminate\Support\Str;
use ReflectionClassConstant;
use App\Enums\Attributes\Description;
trait GetsAttributes
{
/**
* @param self $enum
*/
private static function getDescription(self $enum): string
{
$ref = new ReflectionClassConstant(self::class, $enum->name);
$classAttributes = $ref->getAttributes(Description::class);
if (count($classAttributes) === 0) {
return Str::headline($enum->value);
}
return $classAttributes[0]->newInstance()->description;
}
}
Если мы разберём этот метод, то первые две строки используют отражения для получения атрибутов перечисления. Поскольку не каждое перечисление может иметь атрибут Description
, мы настраиваем запасной вариант, для преобразования значения (или имени) этого перечисления в наше описание (description).
Наконец, мы извлекаем значение описания из атрибутов перечисления. Мы можем добавить ещё один метод к трейту, чтобы справиться с этой задачей.
/**
* @return array<string,string>
*/
public static function asSelectArray(): array
{
/** @var array<string,string> $values */
$values = collect(self::cases())
->map(function ($enum) {
return [
'name' => self::getDescription($enum),
'value' => $enum->value,
];
})->toArray();
return $values;
}
Теперь в HTML мы можем просто изменить метод, вызываемый в классе перечисления enum
.
<select name=”roles”>
@foreach(UserRoles::asSelectArray() as $role)
<option value=”{{ $role->value }}”>{{ $role->name }}</option>
@endforeach
</select>
Хотя перечисления и атрибуты являются относительными новичками в PHP, они отличное дополнение к языку и обеспечивают встроенную поддержку для многих распространённых случаев использования.