• Час читання ~4 хв
  • 27.04.2023

Атрибути PHP були додані у версії 8.0 мови, і це було неправильною назвою для багатьох розробників. Які їх переваги, і як їх можна використовувати?

Це питання, яке я задаю собі з моменту їх випуску, і лише нещодавно я знайшов для них варіант використання. Працюючи над проектом, який потребує доступу до API, я вирішив використовувати Laravel Breeze і додати обгортку навколо токенів API Sanctums замість Jetstream. Це привело мене на шлях з'ясування, як найкраще отримати максимальну віддачу від самих токенів.

Якщо ви раніше користувалися Laravel Jetstream, ви будете знати, що реєструєте дозволи та можливості токенів у Постачальника послуг. Це прийнятний підхід, якщо у вас спрощений API. Однак мої потреби були складнішими, ніж це, але недостатньо складними, щоб потрібно було налаштувати OAuth.

Замість цього я думав, що буду використовувати нативну enum структуру PHP, один з моїх поширених підходів при зберіганні ролей користувачів. Але перерахування недостатньо детальні, що створює проблему. Потім я натрапив на фантастичний і надихаючий підручник Роба Фонески. Він писав про те, як можна розширити перерахування PHP за допомогою атрибутів. Його варіант використання відрізнявся від мого, але вау - мої очі нарешті відкрилися на випадок використання!

Мені потрібно було створити набір дозволів, які дозволяли створеним користувачем токенам API мати певні можливості. Однак я хотів дозволити користувачеві також зрозуміти дозволи, які вони встановлюють. Моїм першим кроком було створення базового enum:

enum Permission: string
{
    case ADMIN = 'ADMIN';
 
    case EDITOR = 'EDITOR';
}

Ці два типи дозволів мають чітке розмежування. Один повинен вміти робити все - в той час як інший має більш обмежений доступ. Тут я озирнувся на підручник Робса і реалізував атрибут опису.

use Attribute;
 
#[Attribute]
final readonly class Description
{
    public function __construct(
        public string $description,
    ) {}
}

Я хотів, щоб мої атрибути були незмінними, щоб ніщо не могло їх змінити. Тож клас лише для читання мав тут великий сенс, а не те, що мені потрібне виправдання ...

Тепер все, що мені потрібно було зробити, це додати атрибут до мого enum:

enum Permission: string
{
    #[Description('Admin users can perform any action.')]
    case ADMIN = 'ADMIN';
 
    #[Description('Editor users have the ability to read, and update.')]
    case EDITOR = 'EDITOR';
}

Це дало інформацію про кожен дозвіл, який я хотів, щоб користувач міг зрозуміти. Однак потім я зіткнувся з іншою проблемою. Як зберігати здібності? Я знав, що перерахування дозволяють лише рядки або цілі числа як регістри, то що я можу зробити?

Знову ж таки, я знайшов свою відповідь в атрибутах - несподівано. Якщо атрибут можна використовувати для додавання опису, його також можна використовувати для додавання інших речей. Тому я створив ще один атрибут під назвою Abilities, який буде використовувати масив рядків, щоб мати підхід до їх перерахування у довільній формі.

#[Attribute]
final readonly class Abilities
{
    public function __construct(
        public array $abilities,
    ) {}
}

Тепер все, що мені потрібно було зробити, це додати це до свого enum, і я міг встановити маркер на enum і використовувати відображення, щоб витягнути здібності під час збереження в базі даних.

enum Permission: string
{
    #[Key('admin')]
    #[Description('Admin users can perform any action.')]
    #[Abilities(['create','read','update','delete'])]
    case ADMIN = 'ADMIN';
 
    #[Key('editor')]
    #[Description('Editor users have the ability to read, and update.')]
    #[Abilities(['read','update'])]
    case EDITOR = 'EDITOR';
}

Ось що я в підсумку отримав. Я хотів, щоб ключ посилання мав приємніший рядок для посилання. Тепер я міг слідувати підручнику Робса і реалізувати спосіб доступу до цих атрибутів.

trait CanAccessAttributes
{
    public static function abilities(BackedEnum $enum): array
    {
        $reflection = new ReflectionClassConstant(
            class: self::class,
            constant: $enum->name,
        );
 
        $attributes = $reflection->getAttributes(
            name: Abilities::class,
        );
 
        if (0 === count($attributes)) {
            return [Str::headline(
                value: strval($enum->value)
            )];
        }
 
        return $attributes[0]->newInstance()->abilities;
    }
 
    public static function key(BackedEnum $enum): string
    {
        $reflection = new ReflectionClassConstant(
            class: self::class,
            constant: $enum->name,
        );
 
        $attributes = $reflection->getAttributes(
            name: Key::class,
        );
 
        if (0 === count($attributes)) {
            return Str::headline(
                value: $enum->value
            );
        }
 
        return $attributes[0]->newInstance()->key;
    }
 
    public static function description(BackedEnum $enum): string
    {
        $reflection = new ReflectionClassConstant(
            class: self::class,
            constant: $enum->name,
        );
 
        $attributes = $reflection->getAttributes(
            name: Description::class,
        );
 
        if (0 === count($attributes)) {
            return Str::headline(
                value: $enum->value
            );
        }
 
        return $attributes[0]->newInstance()->description;
    }
}

Проста риса, яка дозволить мені отримати доступ до всіх атрибутів, які я хотів. Оскільки моя програма використовувала Inertia, все, що мені потрібно було зробити, це передати ресурс у проміжномуHandlesInertia програмному забезпеченні, щоб мій інтерфейс користувача міг отримати доступ до цих дозволів скрізь у деталях. Я вирішив створити для цього ресурс API, щоб я міг послідовно обробляти форматування.

/**
 * @property-read Permission $resource
 */
final class PermissionResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'key' => $this->resource->key($this->resource),
            'name' => $this->resource->name,
            'value' => $this->resource->value,
            'description' => $this->resource->description($this->resource),
            'abilities' => $this->resource->abilities($this->resource),
        ];
    }
}

Нарешті я знайшов варіант використання атрибутів, створюючи те, що я вважаю чудовим способом зареєструвати ці дані у вашій програмі.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297