编程

Laravel 中的 PHP 注解 attributes

402 2023-12-28 04:00:00

Attributes 提供了在代码的声明中添加结构化、机器可读的元数据信息的能力:类、方法、函数、参数、属性和类常量可以是属性的目标。

我相信这个定义是正确的,我相信阅读本文的大多数开发人员都至少遇到过一次 attributes 。如果没有,它们本质上是添加到类中的元数据。

在这一点上,你可能想知道它们与 PHPDOCs 有何不同?好吧,他们是一流的公民,他们是真正的 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']
    ) {
    }
}

就这样,你刚刚创建了一个注解,你还会注意到,我们将其范围限制为类和方法,允许将该注解专门放置在这两个实体上。

现在,我们可以这样使用它


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(),
        ]);
    }
}

come可以从字面上理解,在生产和阶段中忽略它。尽管如此,我们还是需要使其发挥作用,而且实现这一点的方法很少,最简单的是使用中间件。

让我们创建一个中间件,我将其命名为 IsRouteIgnored,可以随意选择您喜欢的任何名称

 

php artisan make:middleware IsRouteIgnored

现在我们可以实现逻辑了,想法很简单:我们拦截使用该中间件的路由的请求,然后检查该操作是否具有 Ignore 属性,如果有,则检查当前环境是否允许具有该路由。

为此,我们将使用反射 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 响应,但将在本地和测试环境中发挥作用。

之后,您可以全局注册中间件或在 AP I路由中注册中间件,就像你常做的那样,并且您可以通过用注解标记路由来开始忽略路由。

结论

只需几行代码,我们就启用了可切换的路由。虽然实现是相对基本的,但该示例旨在展示注解的力量。在我们选择的特定环境中切换路由,您甚至可以调整 Ignore 属性,将路由从除指定环境之外的所有环境中排除,而且选项是无穷无尽的。

下次当你考虑把一个类标记为特定的东西时,考虑给 Attributes 一个机会!

 

 

PHP