• Время чтения ~1 мин
  • 12.02.2024

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

.

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

На этом этапе вам может быть интересно, чем они отличаются от PHPDOC? Ну, они первоклассные граждане, они настоящие классы PHP, и да, я знаю, что теперь это меняет всю игру; Вам не нужно писать регулярные выражения , чтобы извлечь что-то из PHPDocs, и вы даже можете поддерживать некоторую форму состояния в свойствах.

Поскольку я немного опоздал на вечеринку, классических примеров атрибутов предостаточно. Так почему бы вместо этого не построить с ними что-то крутое?

Когда

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

Поэтому, размышляя над этим, я подумал, что было бы здорово, если бы я мог пометить действие как отключенное или игнорируемое. И знаете что? С атрибутами это оказалось очень просто, а также супер чисто.

Начнем с создания атрибута. Я назову свой Ignore, и у него будет единственное свойство под названием in

<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Ignore
{
    public function __construct(
        public array $in = ['production']
    ) {
    }
}

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

Теперь мы можем использовать его так, чтобы

namespace App\Http\Controllers;
use App\Attributes\Ignore;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Symfony\Component\HttpFoundation\Response
class TwoFactorQrCodeController extends Controller
{
    #[Ignore(in: ['production', 'staging'])]
    public function show(Request $request): Response
    {
        if (is_null($request->user()->two_factor_secret)) {
            return [];
        }
        return response()->json([
            'svg' => $request->user()->twoFactorQrCodeSvg(),
            'url' => $request->user()->twoFactorQrCodeUrl(),
        ]);
    }
}

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

Давайте создадим промежуточное ПО, я назову его IsRouteIgnored, не стесняйтесь выбирать любое имя, которое вы предпочитаете

php artisan make:middleware IsRouteIgnored

Теперь мы можем реализовать логику, идея проста: мы перехватываем запросы маршрутов, которые используют это промежуточное ПО, затем мы проверяем, есть Ignore ли у действия атрибут, если да, мы проверяем, разрешено ли текущему окружению иметь этот маршрут или нет.

Для этого мы воспользуемся магией Reflection API, давайте углубимся в код

<?php
namespace App\Http\Middleware;
use Closure;
use ReflectionMethod;
use App\Attributes\Ignore;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Symfony\Component\HttpFoundation\Response;
class IsRouteIgnored
{
    public function handle(Request $request, Closure $next): Response
    {
        $route = $request->route();
        if (!($route instanceof Route) || $route->action['uses'] instanceof Closure) {
            return $next($request);
        }
        $reflection = new ReflectionMethod($route->getControllerClass(), $route->getActionMethod());
        $attributes = $reflection->getAttributes(Ignore::class);
        if (!empty($attributes) && in_array(config('app.env'), $attributes[0]->newInstance()->in)) {
            abort(404);
        }
        return $next($request);
    }
}

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

Теперь мы можем создать экземпляр атрибута, вызвав newInstance(), вернувшись в область обычных классов. Затем мы можем проверить среды, в которых этот маршрут должен игнорироваться в свойстве in . В этом случае маршрут вернет 404 ответ для рабочей и промежуточной сред, но будет работать в локальной и тестовой средах.

После этого вы можете зарегистрировать промежуточное ПО глобально или в маршрутах API, как вы обычно делаете, и вы можете начать игнорировать маршруты, пометив их атрибутом.

Заключение

С помощью всего нескольких строк кода мы включили переключаемые маршруты. Несмотря на то, что реализация была относительно простой, этот пример должен был продемонстрировать мощь атрибутов. Я имею в виду, да ладно, насколько это круто? Переключая маршруты в определенных средах по вашему выбору, вы даже можете настроить атрибут, чтобы исключить маршрут из всех сред, кроме указанных, Ignore и вариантов бесконечное множество.

В следующий раз, когда вы будете задумываться о том, чтобы пометить класс как что-то конкретное, подумайте о том, чтобы попробовать атрибуты! 🪄

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